- Project Overview - What Are We Building?
- Understanding Parkinson's Disease
- Why Voice Analysis Works
- Complete File-by-File Breakdown
- Audio Signal Processing Theory
- Machine Learning Concepts Explained
- Feature Extraction Deep Dive
- Preprocessing Theory (Scaling & PCA)
- Classification Algorithms Explained
- Model Evaluation Metrics
- Complete Data Flow
- How To Run Everything
This project builds a Machine Learning System that can detect whether a person has Parkinson's Disease by analyzing their voice recordings.
Parkinson's Disease affects ~10 million people worldwide. Early detection is crucial but difficult. This system provides:
- Non-invasive testing (just record your voice)
- Quick screening (results in seconds)
- Accessible diagnosis (works with any microphone)
┌─────────────────────────────────────────────────────────────────┐
│ PARKINSON'S DETECTION SYSTEM │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 📁 INPUT: Voice Recording (.wav file) │
│ ↓ │
│ 🎯 FEATURE EXTRACTION (feature_extraction.py) │
│ - Load audio file │
│ - Extract 72 acoustic features │
│ - Save to CSV │
│ ↓ │
│ 🔧 PREPROCESSING │
│ - StandardScaler (normalize values) │
│ - PCA (reduce dimensions) - optional │
│ ↓ │
│ 🤖 MACHINE LEARNING MODEL │
│ - Random Forest / Logistic Regression / SVM │
│ ↓ │
│ 📊 OUTPUT: Prediction (Healthy or Parkinson's) │
│ │
└─────────────────────────────────────────────────────────────────┘
| File | Purpose | Lines of Code |
|---|---|---|
feature_extraction.py |
Extract audio features | ~430 |
train.py |
Train models WITHOUT PCA | ~475 |
pca_train.py |
Train models WITH PCA | ~500 |
predict.py |
Make predictions on new audio | ~700 |
utils.py |
Helper functions | ~360 |
generate_sample_data.py |
Create test audio files | ~325 |
myrec/record.py |
Record audio from mic | ~30 |
Parkinson's Disease is a progressive neurological disorder that affects movement. It occurs when nerve cells in the brain that produce dopamine (a chemical messenger) gradually die.
Parkinson's affects the muscles used for speech, causing:
- Hypophonia - Softer voice volume
- Monopitch - Reduced pitch variation (voice sounds flat)
- Hoarseness - Rough, breathy voice quality
- Tremor - Slight shaking in the voice
- Imprecise articulation - Slurred or unclear speech
These vocal changes are measurable! When we analyze the audio signal mathematically, we can detect:
- Frequency instability (jitter)
- Amplitude variation (shimmer)
- Increased noise (breathiness)
- Reduced harmonic structure
These measurable patterns are what our machine learning model learns to recognize!
Healthy Voice:
┌─────────────────────────────────────┐
│ Regular, smooth vocal fold vibration │
│ → Clean harmonic structure │
│ → Stable frequency │
│ → Clear sound │
└─────────────────────────────────────┘
Parkinson's Voice:
┌─────────────────────────────────────┐
│ Irregular, tremoring vibration │
│ → Distorted harmonics │
│ → Frequency jitter │
│ → Breathier, weaker sound │
└─────────────────────────────────────┘
| Acoustic Property | What It Means | How We Measure It |
|---|---|---|
| Jitter | Frequency variation | MFCC, Spectral features |
| Shimmer | Amplitude variation | Spectral centroid, bandwidth |
| Harmonics-to-Noise Ratio | Breathiness | Zero crossing rate, spectral rolloff |
| Fundamental Frequency | Voice pitch | MFCC coefficients |
librosa>=0.10.0 # Audio analysis library
numpy>=1.24.0 # Numerical computations
pandas>=2.0.0 # Data manipulation
scikit-learn>=1.3.0 # Machine learning
matplotlib>=3.7.0 # Plotting/graphing
seaborn>=0.12.0 # Statistical visualization
joblib>=1.3.0 # Model serialization
soundfile>=0.12.0 # Audio file I/O
scipy>=1.11.0 # Scientific computing
pyaudio>=0.2.13 # Audio recording
sounddevice>=0.4.6 # Alternative audio recording
- librosa: Specialized for music and audio analysis. Has built-in functions for MFCC, chroma, spectral features.
- numpy: All mathematical operations on arrays (audio signals are arrays!)
- pandas: Handles tabular data (our features CSV)
- scikit-learn: All ML algorithms (Random Forest, SVM, etc.)
- matplotlib/seaborn: Create visualizations (confusion matrices, accuracy charts)
- joblib: Save/load trained models efficiently
Creates fake voice recordings for testing when real patient data isn't available.
# Healthy voice characteristics
HEALTHY_F0_MEAN = 150 # Hz (fundamental frequency)
HEALTHY_HARMONICITY = 0.8 # Clear, harmonic sound
# Parkinson's voice characteristics
PARKINSON_F0_MEAN = 130 # Lower pitch
PARKINSON_HARMONICITY = 0.5 # More noise, less harmonic-
Generate a time array:
t = np.linspace(0, duration, n_samples)- Creates evenly spaced time points (e.g., 66,150 points for 3 seconds at 22,050 Hz)
-
Create sine waves (basic sound):
signal = np.sin(2 * np.pi * frequency * t)
- A pure tone at a specific frequency
-
Add harmonics (makes it sound more like a voice):
for harmonic in range(1, 6): signal += (1/harmonic) * np.sin(2 * np.pi * f0 * harmonic * t)
- Real voices have multiple frequencies (fundamental + harmonics)
-
Add noise (for Parkinson's simulation):
signal += noise_level * np.random.randn(n_samples)
- Parkinson's voices are breathier (more noise)
-
Add tremor (amplitude modulation):
signal *= (1 + tremor_depth * np.sin(2 * np.pi * tremor_freq * t))
- Parkinson's causes voice tremor around 5 Hz
generate_sample_data.py
│
↓
┌────────────────────────┐
│ SyntheticAudioGenerator │
├────────────────────────┤
│ • generate_healthy_voice() │
│ • generate_parkinson_voice()│
│ • save_audio() │
└────────────────────────┘
│
↓
┌────────────────────────┐
│ DatasetGenerator │
├────────────────────────┤
│ • Creates healthy/ folder │
│ • Creates parkinson/ folder│
│ • Generates N files each │
└────────────────────────┘
Features are measurable properties of the audio. Think of them as "characteristics" or "attributes" that describe the sound.
┌─────────────────────────────────────────────────────────────┐
│ FEATURE BREAKDOWN │
├─────────────────────────────────────────────────────────────┤
│ MFCC (Mean) : 20 features (mfcc_mean_0 to _19) │
│ MFCC (Std Dev) : 20 features (mfcc_std_0 to _19) │
│ Chroma (Mean) : 12 features (chroma_mean_0 to _11) │
│ Chroma (Std Dev) : 12 features (chroma_std_0 to _11) │
│ Spectral Centroid : 2 features (mean, std) │
│ Spectral Bandwidth : 2 features (mean, std) │
│ Spectral Rolloff : 2 features (mean, std) │
│ Zero Crossing Rate : 2 features (mean, std) │
├─────────────────────────────────────────────────────────────┤
│ TOTAL : 72 FEATURES │
└─────────────────────────────────────────────────────────────┘
class AudioFeatureExtractor:
"""Extracts features from audio files"""
def __init__(self):
# Configure sample rate, number of MFCCs, chroma features
pass
def load_audio(filepath):
# Load .wav file using librosa
pass
def extract_features(audio_signal):
# Compute all 72 features
pass
def process_file(filepath):
# Load + Extract = Complete processing
pass
class DatasetProcessor:
"""Processes entire dataset folders"""
def process():
# Iterate through healthy/ and parkinson/ folders
# Extract features from each file
# Create DataFrame with features + labels
passWhat is MFCC? MFCCs represent the shape of the vocal tract - essentially, how your mouth, throat, and nasal passages shape sound.
How MFCC Works (Step by Step):
Raw Audio → Pre-emphasis → Framing → Windowing → FFT →
Mel Filterbank → Log → DCT → MFCCs
-
Pre-emphasis: Boost high frequencies (compensates for mouth radiation)
y[n] = x[n] - 0.95 * x[n-1] -
Framing: Split audio into small chunks (20-40ms each)
- Voice characteristics change over time, so we analyze short segments
-
Windowing: Apply Hamming window to each frame
- Reduces edge effects (discontinuities at frame boundaries)
-
FFT (Fast Fourier Transform): Convert time → frequency
- Shows which frequencies are present in each frame
-
Mel Filterbank: Map frequencies to Mel scale
- Human hearing is logarithmic (we perceive ratios, not differences)
- 1000 Hz to 2000 Hz sounds like the same "distance" as 2000 Hz to 4000 Hz
-
Log: Take logarithm of filterbank energies
- Compresses dynamic range
-
DCT (Discrete Cosine Transform): Decorrelate coefficients
- Produces the final MFCC values
Why MFCC for Parkinson's? Parkinson's affects vocal tract control, changing the shape of sound. MFCCs capture these subtle changes!
What are Chroma Features? Chroma features represent the pitch class of sound - essentially which musical notes are present.
How it Works:
- Divides audio into 12 pitch classes (C, C#, D, D#, E, F, F#, G, G#, A, A#, B)
- Measures energy in each pitch class over time
Why Chroma for Parkinson's? Parkinson's patients often have monopitch (reduced pitch variation). Chroma features detect this!
Definition: The "center of mass" of the frequency spectrum.
Spectral Centroid = Σ(frequency[i] × magnitude[i]) / Σ(magnitude[i])
Interpretation:
- High centroid = bright, sharp sound
- Low centroid = dull, muffled sound
Why it matters: Parkinson's voices often sound duller (lower centroid)
Definition: How "spread out" the frequencies are around the centroid.
Bandwidth = √(Σ(|frequency[i] - centroid|² × magnitude[i]) / Σ(magnitude[i]))
Interpretation:
- High bandwidth = wide frequency range (rich sound)
- Low bandwidth = narrow frequency range (thin sound)
Definition: The frequency below which 85% of the signal's energy lies.
Interpretation:
- High rolloff = more high-frequency content
- Low rolloff = mostly low frequencies
Why it matters: Parkinson's voices often have less high-frequency energy
Definition: How often the audio signal crosses the zero line (changes from positive to negative).
ZCR = (1 / T) × Σ(sign(x[t]) ≠ sign(x[t-1]))
Interpretation:
- High ZCR = noisy, percussive sound
- Low ZCR = smooth, tonal sound
Why it matters: Parkinson's voices can be breathier (higher ZCR due to noise)
Trains 3 different classifiers on the extracted features and saves the best one.
# 1. Random Forest Classifier
RandomForestClassifier(
n_estimators=100, # 100 decision trees
max_depth=10 # Each tree can have max 10 levels
)
# 2. Logistic Regression
LogisticRegression(
max_iter=1000, # Maximum iterations for convergence
C=1.0 # Regularization strength
)
# 3. Support Vector Machine
SVC(
kernel='rbf', # Radial Basis Function kernel
C=1.0, # Regularization parameter
gamma='scale' # Kernel coefficient
)┌─────────────────────────────────────────────────────────────┐
│ TRAINING PIPELINE │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. LOAD DATA │
│ └─→ Read features.csv │
│ │
│ 2. SPLIT DATA │
│ └─→ 85% training, 15% testing (stratified) │
│ │
│ 3. PREPROCESS │
│ └─→ StandardScaler (normalize all features) │
│ │
│ 4. CREATE MODELS │
│ └─→ Initialize RF, LR, SVM │
│ │
│ 5. TRAIN MODELS │
│ └─→ Fit each model on training data │
│ │
│ 6. EVALUATE │
│ └─→ Compute accuracy, precision, recall, F1 │
│ │
│ 7. SAVE MODELS │
│ └─→ Save to ./models/ as .joblib files │
│ │
│ 8. GENERATE PLOTS │
│ └─→ Confusion matrices, accuracy charts │
│ │
└─────────────────────────────────────────────────────────────┘
TEST_SIZE = 0.15 # 15% of data for testing
RANDOM_STATE = 42 # For reproducibility (same split every time)train_test_split(..., stratify=y)Stratification ensures both train and test sets have the same proportion of healthy/Parkinson's samples as the original data.
Example: If data is 60% healthy, 40% Parkinson's:
- Training set: 60% healthy, 40% Parkinson's ✓
- Test set: 60% healthy, 40% Parkinson's ✓
Without stratification, you might get unlucky and have mostly one class in your test set!
PCA (Principal Component Analysis) is a dimensionality reduction technique.
Problem: We have 72 features. Some might be correlated (e.g., mfcc_mean_0 and mfcc_std_0 might be related). This is called multicollinearity.
Solution: PCA creates new features (principal components) that:
- Are uncorrelated with each other
- Capture the most important information in fewer dimensions
Step 1: Standardize the data (mean = 0, variance = 1)
Step 2: Compute the covariance matrix
Step 3: Calculate eigenvectors and eigenvalues
Step 4: Sort eigenvectors by eigenvalues (most important first)
Step 5: Select top N components that retain desired variance
Step 6: Transform data to new feature space
Original 2D Data: After PCA (1 Component):
● ● ● ●
● ● ● ●●
● ● ● ●●●
● ● ● ●●
● ● ● ●●
● ● ● ●
●● ●
→ PCA finds the direction of maximum variance
→ Projects data onto that direction
→ Reduces 2D → 1D while keeping most information
PCA_VARIANCE = 0.95 # Retain 95% of the varianceThis means PCA will automatically select enough components to capture 95% of the information in the original 72 features.
Typical Result: 72 features → ~40-50 principal components (40-50% reduction!)
Same as train.py but with an extra step:
PREPROCESS → APPLY PCA → CREATE MODELS → TRAIN → EVALUATE
Load a trained model and predict whether a new audio file indicates Parkinson's.
┌─────────────────────────────────────────────────────────────┐
│ PREDICTION PIPELINE │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. LOAD BEST MODEL │
│ └─→ Read best_model.txt to find best model │
│ └─→ Load model, scaler, (optional PCA) from ./models/ │
│ │
│ 2. LOAD AUDIO FILE │
│ └─→ Validate file exists and is .wav │
│ │
│ 3. EXTRACT FEATURES │
│ └─→ Same 72 features as training │
│ │
│ 4. PREPROCESS │
│ └─→ Apply same StandardScaler from training │
│ └─→ Apply same PCA if model uses PCA │
│ │
│ 5. PREDICT │
│ └─→ model.predict(features) → 0 or 1 │
│ └─→ model.predict_proba(features) → confidence % │
│ │
│ 6. DISPLAY RESULTS │
│ └─→ Print prediction and confidence │
│ │
└─────────────────────────────────────────────────────────────┘
IMPORTANT: The SAME scaler and PCA from training MUST be used during prediction!
# During training
scaler.fit(X_train) # Learn mean and std from training data
X_train_scaled = scaler.transform(X_train)
# During prediction
X_pred_scaled = scaler.transform(X_pred) # Use SAME scaler!If you fit a new scaler on prediction data, the model will fail!
python predict.py --record --duration 5This records 5 seconds from your microphone and predicts immediately.
How it works:
- Opens audio stream using PyAudio
- Records for specified duration
- Saves to temporary .wav file
- Runs prediction pipeline
- Deletes temporary file
| Function | Purpose |
|---|---|
compute_metrics() |
Calculate accuracy, precision, recall, F1 |
get_classification_report() |
Generate detailed report string |
plot_confusion_matrix() |
Create and save confusion matrix |
plot_accuracy_comparison() |
Bar chart comparing models |
plot_pca_variance() |
Scree plot for PCA |
plot_feature_importance() |
Show which features matter most |
save_model() |
Save model to disk |
load_model() |
Load model from disk |
save_scaler() |
Save scaler to disk |
save_pca() |
Save PCA object to disk |
save_results_to_csv() |
Save metrics to CSV |
save_best_model() |
Save name of best model |
get_best_model() |
Find best model by metric |
Records multiple audio samples from microphone.
# 1. Create output folder
os.makedirs(folder, exist_ok=True)
# 2. Loop for N recordings
for i in range(num_recordings):
# Wait for user to press Enter
input("Press Enter to record...")
# Record for 'duration' seconds
audio = sd.rec(int(duration * sample_rate), ...)
sd.wait() # Wait for recording to finish
# Save to file
write(filename, sample_rate, audio)Sound is a pressure wave traveling through air (or another medium).
Amplitude (loudness)
↑
│ ╱‾‾╲ ╱‾‾╲
│ ╱ ╲ ╱ ╲
────────┼───╱──────╲──╱──────╲───→ Time
│ ╱ ╲╱ ╲
│ ╱ ╲
↓
Wavelength → distance between peaks
Frequency → 1/wavelength (pitch)
When we digitize sound:
- Sampling: Measure amplitude at regular intervals
- Sample Rate: How many measurements per second (Hz)
- CD quality: 44,100 Hz (44,100 samples per second)
- Our project: 22,050 Hz (sufficient for voice)
Analog Sound: ~~~~~~~~continuous wave~~~~~~~~
↓ (sampling)
Digital Sound: _▔▔_▔▔_▔▔_▔▔_▔▔_▔▔_▔▔_▔▔_
↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
discrete samples
Shows how amplitude changes over time.
- What you see in an audio editor waveform
Shows which frequencies are present and their strengths.
- What you see in a spectrum analyzer
The mathematical operation that converts time → frequency.
Time Domain Signal ──[FFT]──→ Frequency Spectrum
FFT (Fast Fourier Transform) is the efficient algorithm for this.
┌─────────────────────────────────────────────────┐
│ VOICE PRODUCTION │
├─────────────────────────────────────────────────┤
│ │
│ Lungs → Air pressure │
│ ↓ │
│ Vocal Folds → Vibration (creates sound) │
│ ↓ │
│ Vocal Tract → Shapes the sound │
│ (throat, mouth, tongue, lips) │
│ ↓ │
│ Radiated Sound → What we hear │
│ │
└─────────────────────────────────────────────────┘
| Component | Parkinson's Effect | Acoustic Result |
|---|---|---|
| Vocal Folds | Reduced control, tremor | Jitter, shimmer |
| Vocal Tract | Stiffness, reduced movement | Formant changes |
| Respiratory | Weaker breath support | Lower volume |
Machine Learning is teaching computers to learn patterns from data instead of explicitly programming rules.
TRADITIONAL:
Input + Rules → Output
MACHINE LEARNING:
Input + Output → Rules (Model)
Then:
New Input + Rules → Predicted Output
Voice Features + Labels (Healthy/Parkinson's) → Train Model
Then:
New Voice Features + Trained Model → Prediction
Our project uses Supervised Learning - we have labeled examples.
Training Data:
┌────────────────────────────────┐
│ Features (X) │ Label (y) │
├────────────────────────────────┤
│ [0.5, 0.3, ...] │ Healthy (0) │
│ [0.8, 0.9, ...] │ Parkinson (1)│
│ [0.2, 0.1, ...] │ Healthy (0) │
│ ... │ ... │
└────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ ML WORKFLOW │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ DATA │ → │ PREPROCESS │ → │ TRAIN │ │
│ │ COLLECTION │ │ │ │ MODEL │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ ↑ │ │
│ │ ↓ │
│ │ ┌─────────────┐ │
│ │ │ EVALUATE │ │
│ │ │ MODEL │ │
│ │ └─────────────┘ │
│ │ │ │
│ │ ↓ │
│ │ ┌─────────────┐ │
│ └────────────────────────────│ DEPLOY │ │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
Garbage In, Garbage Out - No matter how good your model is, if your features don't capture relevant information, predictions will be poor.
| Approach | Description | Our Project |
|---|---|---|
| Feature Engineering | Manually design features based on domain knowledge | ✓ We do this |
| Feature Learning | Let neural network learn features automatically | Not used here |
Research has shown that Parkinson's affects:
- Vocal fold vibration → MFCC captures this
- Pitch control → Chroma features capture this
- Voice quality → Spectral features capture this
- Breathiness → Zero crossing rate captures this
For each feature type, we compute both:
- Mean: Average value across all time frames
- Standard Deviation: How much the feature varies over time
Healthy Voice:
MFCC over time: ━━━━ (stable, low std dev)
Parkinson's Voice:
MFCC over time: ╱╲╱╲╱╲ (variable, high std dev)
The variation itself is a symptom!
Our features have different scales:
- MFCC values: -500 to +500
- Zero Crossing Rate: 0 to 1
- Spectral Centroid: 0 to 5000 Hz
Problem: ML algorithms treat larger numbers as more important!
X_scaled = (X - mean) / std
After scaling:
- All features have mean = 0
- All features have std = 1
- All features are on the same scale
Before Scaling:
MFCC: ████████████████████████ (range: -500 to 500)
ZCR: ██ (range: 0 to 1)
After Scaling:
MFCC: ████████████ (range: -3 to 3)
ZCR: ████████████ (range: -3 to 3)
scaler.fit(X_train) # Learn mean, std from training
scaler.transform(X_train) # Transform training
scaler.transform(X_test) # Transform test (using TRAIN stats!)Reason: In real deployment, you won't know the statistics of incoming data. Using test data statistics would be "data leakage" - cheating!
Goal: Find new axes (principal components) that capture maximum variance.
Step 1: Center the data
────────────────────────
Subtract mean from each feature.
Original: Centered:
●● ●
● ● ● ●
●● ●
↓
Mean at origin (0,0)
Step 2: Compute covariance matrix
─────────────────────────────────
Shows how features vary together.
Covariance(X, Y) = E[(X-μₓ)(Y-μᵧ)]
Positive covariance: X and Y increase together
Negative covariance: X increases when Y decreases
Zero covariance: X and Y are independent
Step 3: Calculate eigenvectors and eigenvalues
──────────────────────────────────────────────
Eigenvector: Direction of maximum variance
Eigenvalue: How much variance is in that direction
┌───────────────┐
│ Covariance │ × │ Eigenvector │ = │ Eigenvalue │ × │ Eigenvector │
│ Matrix │ │ │ │ │ │ │
└───────────────┘ └───────────────┘ └────────────┘ └───────────────┘
Step 4: Sort and select components
───────────────────────────────────
Sort eigenvectors by eigenvalue (largest first).
Select top N that retain desired variance.
Variance retained = Σ(selected eigenvalues) / Σ(all eigenvalues)
Step 5: Transform data
──────────────────────
Project data onto selected eigenvectors.
X_new = X_original × Eigenvectors_selected
| Pros | Cons |
|---|---|
| Reduces dimensionality | Loss of interpretability |
| Removes multicollinearity | Information loss (though controlled) |
| Faster training | Extra computation for PCA itself |
| Can improve accuracy | May remove useful non-linear patterns |
Without PCA:
- Uses all 72 features
- Model might overfit
- More interpretable (feature importance makes sense)
With PCA:
- Uses ~40-50 components
- Faster prediction
- May generalize better
Random Forest is an ensemble of Decision Trees.
A decision tree asks a series of yes/no questions:
Is mfcc_mean_0 > 0.5?
╱ ╲
YES NO
╱ ╲
Is chroma_mean_3 > 0.3? Is zcr_mean > 0.05?
╱ ╲ ╱ ╲
YES NO YES NO
╱ ╲ ╱ ╲
Parkinson's Healthy Parkinson's Healthy
- Start: All training samples at root
- Find best split: Which feature and threshold best separates classes?
- Split: Divide samples into two groups
- Repeat: For each group, find next best split
- Stop: When max depth reached or samples too few
Gini = 1 - Σ(p_i)²
Where p_i = proportion of class i in the node
Perfect split: Gini = 0 (all one class)
Worst split: Gini = 0.5 (50-50 mix)
Random Forest = Tree 1 + Tree 2 + Tree 3 + ... + Tree 100
↓ ↓ ↓ ↓
Prediction Prediction Prediction Prediction
╲ │ │ ╱
╲ │ │ ╱
╲ │ │ ╱
╲ │ │ ╱
╲ │ │ ╱
╲ │ │ ╱
╲ │ │ ╱
╲ │ │ ╱
╲ │ │ ╱
╲│ │ ╱
↓ ↓ ↓
MAJORITY VOTE → Final Prediction
Two sources of randomness:
- Bootstrap Sampling: Each tree trains on a random subset of data (with replacement)
- Random Features: At each split, only a random subset of features is considered
| Advantage | Explanation |
|---|---|
| Handles high dimensions | Works well with 72 features |
| Robust to outliers | Individual trees may overfit, but ensemble averages out |
| No preprocessing needed | Doesn't require scaling (but we do it anyway for other models) |
| Feature importance | Tells us which features matter most |
| Non-linear | Captures complex patterns |
RandomForestClassifier(
n_estimators=100, # 100 trees
max_depth=10, # Each tree max 10 levels deep
random_state=42 # Reproducible randomness
)Logistic Regression predicts the PROBABILITY of belonging to a class.
Linear Combination: z = w₁x₁ + w₂x₂ + ... + wₙxₙ + b
Sigmoid Function: P(class=1) = σ(z) = 1 / (1 + e⁻ᶻ)
Prediction: If P > 0.5 → class 1, else class 0
Probability
1.0 │ ╱‾‾‾‾‾‾‾
│ ╱
0.5 │──────────────╱────────────── ← Decision boundary
│ ╱
0.0 │──────╱‾‾
│
└─────────────────────────────────→ z (linear combination)
The model learns weights (w) by minimizing a loss function.
Loss Function: Log Loss (Binary Cross-Entropy)
Loss = -[y × log(p) + (1-y) × log(1-p)]
Where:
- y = true label (0 or 1)
- p = predicted probability
Goal: Find weights that minimize total loss across all training samples.
1. Start with random weights
2. Compute gradient (slope) of loss function
3. Move weights in direction that reduces loss
4. Repeat until convergence
w_new = w_old - learning_rate × gradient
Problem: Model might overfit (memorize training data).
Solution: Add penalty for large weights.
Regularized Loss = Original Loss + λ × ||w||²
Where λ (lambda) controls regularization strength.
LogisticRegression(
max_iter=1000, # Maximum optimization steps
C=1.0, # Inverse of regularization strength
solver='lbfgs' # Optimization algorithm
)- Fast to train and predict
- Provides probability estimates
- Works well as a baseline
- Less prone to overfitting than complex models
SVM finds the optimal boundary (hyperplane) that separates classes with maximum margin.
● ● ← Healthy (class 0)
─────────────────── ← Decision boundary
○ ○ ← Parkinson's (class 1)
SVM finds the boundary that maximizes distance to nearest points of each class.
│
● │ margin
│◄──┼───►│
────┼───┼───┼──── ← Hyperplane
│ │ │
│ └───┘
│ ○
│
Support Vectors: The points closest to the boundary (they "support" it)
Problem: Data might not be linearly separable.
Solution: Transform to higher dimensions where it IS separable.
2D Data (not separable): 3D Data (separable!):
● ○ ● ● ●
○ ● ○ ○ ○
● ○ ● ●
Transform: (x₁, x₂) → (x₁, x₂, x₁² + x₂²)
RBF (Radial Basis Function) Kernel:
K(x, x') = exp(-γ × ||x - x'||²)
Where:
- γ (gamma) controls how far each sample's influence reaches
- ||x - x'|| is the Euclidean distance between samples
Intuition: Each training sample creates a "hill" of influence. Prediction is based on nearby samples.
Small C: Large margin, more misclassifications allowed (underfitting risk)
Large C: Small margin, fewer misclassifications (overfitting risk)
SVC(
kernel='rbf', # RBF kernel for non-linear boundaries
C=1.0, # Balanced regularization
gamma='scale', # Auto-compute gamma
probability=True # Enable probability estimates
)- Works well in high-dimensional spaces (72 features)
- Effective when number of features > number of samples
- Robust to overfitting with proper regularization
- Handles non-linear patterns via kernel trick
| Aspect | Random Forest | Logistic Regression | SVM |
|---|---|---|---|
| Complexity | Medium | Low | High |
| Training Speed | Medium | Fast | Slow |
| Prediction Speed | Medium | Fast | Medium |
| Interpretability | Good (feature importance) | Good (coefficients) | Poor |
| Handles Non-linearity | Yes | No (without features) | Yes (kernel trick) |
| Sensitive to Scaling | No | Yes | Yes |
| Memory Usage | High (stores trees) | Low | Medium |
Accuracy alone can be misleading!
Dataset: 95 healthy, 5 Parkinson's
Dumb Model: Always predict "healthy"
Accuracy: 95% ← Looks good!
But: Misses ALL Parkinson's cases ← Useless!
┌─────────────────────────────────────────────────┐
│ CONFUSION MATRIX │
├─────────────────────────────────────────────────┤
│ │
│ PREDICTED │
│ Healthy Parkinson │
│ ┌─────────────────────────────────┐ │
│ Healthy│ TN (True Negative) │ FP │ │
│ ACTUAL │ │ (False Positive)│
│ ├─────────────────────────────────┤ │
│ Parkinson│ FN (False Negative)│ TP │ │
│ │ │ (True Positive)│
│ └─────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────┘
| Term | Meaning | Example |
|---|---|---|
| TP (True Positive) | Correctly predicted Parkinson's | Patient has PD, model says PD ✓ |
| TN (True Negative) | Correctly predicted Healthy | Healthy person, model says healthy ✓ |
| FP (False Positive) | Wrongly predicted Parkinson's | Healthy person, model says PD ✗ |
| FN (False Negative) | Wrongly predicted Healthy | Patient has PD, model says healthy ✗ |
Accuracy = (TP + TN) / (TP + TN + FP + FN)
Meaning: Overall correctness
Precision = TP / (TP + FP)
Meaning: Of all predicted Parkinson's, how many actually have it?
When it matters: False positives are costly (unnecessary stress, tests)
Recall = TP / (TP + FN)
Meaning: Of all actual Parkinson's patients, how many did we catch?
When it matters: False negatives are dangerous (missing diagnosis)
F1 = 2 × (Precision × Recall) / (Precision + Recall)
Meaning: Harmonic mean of precision and recall
When it matters: You need a balance of both
RECALL is most important!
Reason: Missing a Parkinson's diagnosis (false negative) is worse than a false alarm. Better to flag someone for further testing than miss early treatment opportunity.
From model_results.csv:
Model | Accuracy | Precision | Recall | F1
─────────────────────────────────────────────────────────
SVM (no PCA) | 85.98% | 85.99% | 85.98% | 85.98%
Random Forest | 82.12% | 82.30% | 82.12% | 82.11%
Logistic Regression| 75.06% | 75.06% | 75.06% | 75.06%
SVM (with PCA) | 83.55% | 83.55% | 83.55% | 83.55%
Random Forest (PCA)| 75.72% | 75.72% | 75.72% | 75.71%
Logistic Reg (PCA) | 67.66% | 67.66% | 67.66% | 67.66%
Best Model: SVM without PCA (85.98% accuracy)
┌─────────────────────────────────────────────────────────────────┐
│ TRAINING PHASE │
└─────────────────────────────────────────────────────────────────┘
Step 1: Data Collection
────────────────────────
┌─────────────────────┐
│ dataset/ │
│ ├── healthy/ │ → 50+ .wav files
│ └── parkinson/ │ → 50+ .wav files
└─────────────────────┘
│
↓
Step 2: Feature Extraction (feature_extraction.py)
──────────────────────────────────────────────────
┌─────────────────────────────────────────────────┐
│ For each .wav file: │
│ 1. Load audio (librosa.load) │
│ 2. Extract 72 features │
│ 3. Store in DataFrame with label │
└─────────────────────────────────────────────────┘
│
↓
┌───────────────┐
│ features.csv │ (rows = samples, cols = features + label)
└───────────────┘
│
↓
Step 3: Training (train.py OR pca_train.py)
───────────────────────────────────────────
┌─────────────────────────────────────────────────┐
│ 1. Load features.csv │
│ 2. Split: 85% train, 15% test │
│ 3. Apply StandardScaler │
│ 4. (Optional) Apply PCA │
│ 5. Train 3 models: RF, LR, SVM │
│ 6. Evaluate on test set │
│ 7. Save best model │
└─────────────────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────┐
│ models/ │
│ ├── RandomForest_no_pca.joblib │
│ ├── LogisticRegression_no_pca.joblib │
│ ├── SVM_no_pca.joblib │
│ ├── scaler_no_pca.joblib │
│ └── (PCA versions if pca_train.py) │
└─────────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────┐
│ plots/ │
│ ├── confusion_matrix_*.png │
│ ├── accuracy_comparison_*.png │
│ └── feature_importance_*.png │
└─────────────────────────────────────────┘
│
↓
┌─────────────────┐
│ best_model.txt │ → Contains "SVM"
└─────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ PREDICTION PHASE │
└─────────────────────────────────────────────────────────────────┘
Step 1: User Provides Audio
───────────────────────────
┌──────────────────┐
│ my_voice.wav │ (new, unseen audio file)
└──────────────────┘
│
↓
Step 2: Load Model (predict.py)
───────────────────────────────
┌─────────────────────────────────────────────────┐
│ 1. Read best_model.txt → "SVM" │
│ 2. Load SVM_no_pca.joblib │
│ 3. Load scaler_no_pca.joblib │
│ 4. Initialize feature extractor │
└─────────────────────────────────────────────────┘
│
↓
Step 3: Process Audio
─────────────────────
┌─────────────────────────────────────────────────┐
│ 1. Load audio file │
│ 2. Extract same 72 features │
│ 3. Apply same scaler │
│ (scaler.transform, NOT fit!) │
└─────────────────────────────────────────────────┘
│
↓
Step 4: Predict
───────────────
┌─────────────────────────────────────────────────┐
│ 1. model.predict(features) → 0 or 1 │
│ 2. model.predict_proba(features) → [0.15, 0.85]│
│ 3. Confidence = 85% │
└─────────────────────────────────────────────────┘
│
↓
Step 5: Output Result
─────────────────────
╔═══════════════════════════════════════════════╗
║ PARKINSON'S DISEASE DETECTION - PREDICTION ║
╠═══════════════════════════════════════════════╣
║ Using Model: SVM ║
║ Prediction: Parkinson Detected ║
║ Confidence: 85.00% ║
╚═══════════════════════════════════════════════╝
# Clone or navigate to project
cd /Users/nishantkumarpubgplayergmail.com/Desktop/myProject
# Install dependencies
pip install -r requirements.txt# macOS
brew install portaudio
pip install pyaudio
# Linux
sudo apt-get install portaudio19-dev
pip install pyaudio
# Windows
pip install pyaudio
# Or download from: https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyaudio# Step 1: Generate fake voice recordings
python generate_sample_data.py --output_dir ./dataset --samples 200
# Step 2: Extract features
python feature_extraction.py --data_dir ./dataset --output features.csv
# Step 3: Train models (without PCA)
python train.py --data features.csv --output_dir ./models
# Step 4: Train models (with PCA)
python pca_train.py --data features.csv --output_dir ./models --variance 0.95
# Step 5: Make predictions
python predict.py --file path/to/audio.wav# Step 1: Place your .wav files in dataset/healthy/ and dataset/parkinson/
# Step 2: Extract features
python feature_extraction.py --data_dir ./dataset --output features.csv
# Step 3-5: Same as above# Record from microphone and predict immediately
python predict.py --record --duration 5python feature_extraction.py --data_dir ./dataset --output features.csv
python feature_extraction.py -d ./data -o my_features.csv# Without PCA
python train.py --data features.csv --output_dir ./models
# With PCA (95% variance)
python pca_train.py --data features.csv --variance 0.95
# With PCA (90% variance)
python pca_train.py --data features.csv --variance 0.90# Basic prediction
python predict.py --file audio.wav
# Use specific model
python predict.py --file audio.wav --model RandomForest
# Verbose output
python predict.py --file audio.wav --verbose
# Record and predict
python predict.py --record --duration 52024-04-30 12:00:00 - INFO - Processing healthy control files...
100%|████████████████████| 50/50 [00:15<00:00, 3.2it/s]
2024-04-30 12:00:15 - INFO - Processing Parkinson's disease files...
100%|████████████████████| 50/50 [00:16<00:00, 3.1it/s]
============================================================
FEATURE EXTRACTION SUMMARY
============================================================
Total files processed: 100
Total files failed: 0
Healthy samples: 50
Parkinson samples: 50
Total features: 72
============================================================
============================================================
TRAINING MODELS
============================================================
Training RandomForest...
✓ RandomForest training complete
Training LogisticRegression...
✓ LogisticRegression training complete
Training SVM...
✓ SVM training complete
============================================================
EVALUATING MODELS
============================================================
RandomForest:
Accuracy: 0.8212
Precision: 0.8230
Recall: 0.8212
F1 Score: 0.8211
==================================================
PARKINSON'S DISEASE DETECTION - PREDICTION
==================================================
Using Model: SVM
Prediction: Parkinson Detected
Confidence: 87.45%
==================================================
myProject/
├── 📄 README.md # Project overview
├── 📄 requirements.txt # Python dependencies
├── 📄 generate_sample_data.py # Synthetic data generator
├── 📄 feature_extraction.py # Audio feature extractor
├── 📄 train.py # Model training (no PCA)
├── 📄 pca_train.py # Model training (with PCA)
├── 📄 predict.py # Prediction pipeline
├── 📄 utils.py # Helper functions
├── 📄 features.csv # Extracted features
├── 📄 model_results.csv # Model performance metrics
├── 📄 best_model.txt # Name of best model
├── 📄 best_model_no_pca.txt # Best model without PCA
│
├── 📁 dataset/ # Audio data directory
│ ├── healthy/ # Healthy control recordings
│ └── parkinson/ # Parkinson's patient recordings
│
├── 📁 models/ # Saved models
│ ├── RandomForest_no_pca.joblib
│ ├── LogisticRegression_no_pca.joblib
│ ├── SVM_no_pca.joblib
│ ├── RandomForest_pca.joblib
│ ├── LogisticRegression_pca.joblib
│ ├── SVM_pca.joblib
│ ├── scaler_no_pca.joblib
│ ├── scaler_pca.joblib
│ └── pca_model.joblib
│
├── 📁 plots/ # Generated visualizations
│ ├── confusion_matrix_*.png
│ ├── accuracy_comparison_*.png
│ ├── feature_importance_*.png
│ └── pca_explained_variance.png
│
└── 📁 myrec/
└── record.py # Simple audio recorder
| Error | Cause | Solution |
|---|---|---|
ModuleNotFoundError: No module named 'librosa' |
Dependencies not installed | pip install -r requirements.txt |
FileNotFoundError: best_model.txt |
Training not completed | Run train.py and pca_train.py first |
ValueError: Healthy directory not found |
Wrong data directory structure | Create dataset/healthy/ and dataset/parkinson/ folders |
ImportError: PyAudio not available |
PyAudio not installed | Follow PyAudio installation instructions |
Feature vector length mismatch |
Audio file corrupt or too short | Use valid .wav files with sufficient duration |
| Term | Definition |
|---|---|
| Audio Signal | Digital representation of sound as amplitude values over time |
| Feature | Measurable property used as input to ML model |
| MFCC | Mel-Frequency Cepstral Coefficients - represents vocal tract shape |
| Chroma | Pitch class features representing musical notes |
| Spectral | Related to the frequency spectrum of sound |
| StandardScaler | Normalizes features to have mean=0, std=1 |
| PCA | Principal Component Analysis - dimensionality reduction |
| Random Forest | Ensemble of decision trees |
| Logistic Regression | Linear classifier using sigmoid function |
| SVM | Support Vector Machine - maximum margin classifier |
| Confusion Matrix | Table showing TP, TN, FP, FN counts |
| Precision | TP / (TP + FP) - accuracy of positive predictions |
| Recall | TP / (TP + FN) - ability to find all positives |
| F1 Score | Harmonic mean of precision and recall |
| Overfitting | Model memorizes training data, fails on new data |
| Underfitting | Model too simple to capture patterns |
This project demonstrates a complete end-to-end machine learning pipeline for a real-world medical application. You've learned about:
- Audio Processing: How to extract meaningful features from sound
- Feature Engineering: Why domain knowledge matters for feature selection
- Preprocessing: Scaling and dimensionality reduction techniques
- Classification: Three different algorithms with different strengths
- Evaluation: Multiple metrics to properly assess model performance
- Deployment: How to save models and make predictions on new data
The system achieves ~86% accuracy with SVM, demonstrating that voice analysis is a viable approach for Parkinson's disease screening.
MFCCs capture the vocal tract shape - the configuration of mouth, throat, and nasal passages.
| Feature Name | Index | What It Measures | Parkinson's Effect | Typical Range |
|---|---|---|---|---|
mfcc_mean_0 |
0 | Overall energy/loudness | Reduced (hypophonia) | -500 to 500 |
mfcc_mean_1 |
1 | First spectral shape | Flattened | -50 to 50 |
mfcc_mean_2 |
2 | Second spectral shape | Altered formants | -50 to 50 |
mfcc_mean_3 |
3 | Third spectral shape | Reduced articulation | -50 to 50 |
mfcc_mean_4 |
4 | Fourth spectral shape | Muscle control loss | -50 to 50 |
mfcc_mean_5 |
5 | Fifth spectral shape | Tremor effect | -30 to 30 |
mfcc_mean_6 |
6 | Sixth spectral shape | Harmonic changes | -30 to 30 |
mfcc_mean_7 |
7 | Seventh spectral shape | Voice quality | -25 to 25 |
mfcc_mean_8 |
8 | Eighth spectral shape | Breathiness | -25 to 25 |
mfcc_mean_9 |
9 | Ninth spectral shape | Vocal fold tension | -20 to 20 |
mfcc_mean_10 |
10 | Tenth spectral shape | Pitch stability | -20 to 20 |
mfcc_mean_11 |
11 | Eleventh spectral shape | Resonance | -15 to 15 |
mfcc_mean_12 |
12 | Twelfth spectral shape | Formant bandwidth | -15 to 15 |
mfcc_mean_13 |
13 | Thirteenth spectral shape | Voice breaks | -12 to 12 |
mfcc_mean_14 |
14 | Fourteenth spectral shape | Micro-tremor | -12 to 12 |
mfcc_mean_15 |
15 | Fifteenth spectral shape | Fine structure | -10 to 10 |
mfcc_mean_16 |
16 | Sixteenth spectral shape | Subtle variations | -10 to 10 |
mfcc_mean_17 |
17 | Seventeenth spectral shape | High-frequency content | -8 to 8 |
mfcc_mean_18 |
18 | Eighteenth spectral shape | Very high frequencies | -8 to 8 |
mfcc_mean_19 |
19 | Nineteenth spectral shape | Finest details | -5 to 5 |
These measure variability - how much each MFCC coefficient changes over time.
| Feature Name | Index | What It Measures | Parkinson's Effect |
|---|---|---|---|
mfcc_std_0 |
20 | Energy variability | Increased (tremor) |
mfcc_std_1 |
21 | Spectral shape stability | More variable |
mfcc_std_2 |
22 | Formant stability | Increased variation |
mfcc_std_3 |
23 | Articulation consistency | Less consistent |
mfcc_std_4 |
24 | Muscle control stability | More erratic |
mfcc_std_5 |
25 | Tremor magnitude | Higher values |
mfcc_std_6 |
26 | Harmonic stability | Less stable |
mfcc_std_7 |
27 | Voice quality consistency | More variable |
mfcc_std_8 |
28 | Breathiness consistency | Variable |
mfcc_std_9 |
29 | Vocal tension changes | More fluctuation |
mfcc_std_10 |
30 | Pitch variation | Monopitch (lower) |
mfcc_std_11 |
31 | Resonance stability | Variable |
mfcc_std_12 |
32 | Formant bandwidth changes | Inconsistent |
mfcc_std_13 |
33 | Voice break frequency | More breaks |
mfcc_std_14 |
34 | Micro-tremor variability | Higher |
mfcc_std_15 |
35 | Fine structure stability | Less stable |
mfcc_std_16 |
36 | Subtle variation consistency | Erratic |
mfcc_std_17 |
37 | High-freq content stability | Variable |
mfcc_std_18 |
38 | Very high-freq stability | Inconsistent |
mfcc_std_19 |
39 | Detail-level stability | Less consistent |
Why Both Mean and Std?
- Mean tells us the average vocal tract configuration
- Std tells us how stable/controlled that configuration is
- Parkinson's shows as BOTH altered means AND increased variability
Chroma features represent pitch classes - the 12 musical notes.
| Feature Name | Index | Musical Note | Frequency (Hz)* | Parkinson's Effect |
|---|---|---|---|---|
chroma_mean_0 |
40 | C | ~261.6 | Reduced energy in lower notes |
chroma_mean_1 |
41 | C# | ~277.2 | Monopitch effect |
chroma_mean_2 |
42 | D | ~293.7 | Limited pitch range |
chroma_mean_3 |
43 | D# | ~311.1 | Flattened melody |
chroma_mean_4 |
44 | E | ~329.6 | Reduced variation |
chroma_mean_5 |
45 | F | ~349.2 | Narrow pitch span |
chroma_mean_6 |
46 | F# | ~370.0 | Less pitch movement |
chroma_mean_7 |
47 | G | ~392.0 | Monotone speech |
chroma_mean_8 |
48 | G# | ~415.3 | Reduced prosody |
chroma_mean_9 |
49 | A | ~440.0 | Flat intonation |
chroma_mean_10 |
50 | A# | ~466.2 | Limited expression |
chroma_mean_11 |
51 | B | ~493.9 | Reduced high pitches |
*Relative to fundamental frequency, not absolute
| Feature Name | Index | What It Measures | Parkinson's Effect |
|---|---|---|---|
chroma_std_0 |
52 | C pitch variability | Lower (monopitch) |
chroma_std_1 |
53 | C# pitch variability | Reduced movement |
chroma_std_2 |
54 | D pitch variability | Less variation |
chroma_std_3 |
55 | D# pitch variability | Flatter |
chroma_std_4 |
56 | E pitch variability | Reduced |
chroma_std_5 |
57 | F pitch variability | Lower std |
chroma_std_6 |
58 | F# pitch variability | Less movement |
chroma_std_7 |
59 | G pitch variability | Monotone |
chroma_std_8 |
60 | G# pitch variability | Reduced |
chroma_std_9 |
61 | A pitch variability | Lower |
chroma_std_10 |
62 | A# pitch variability | Less |
chroma_std_11 |
63 | B pitch variability | Reduced |
Why Chroma for Parkinson's?
- Parkinson's causes monopitch - reduced pitch variation
- Chroma features directly measure pitch class energy
- Standard deviation shows how much pitch varies (lower in Parkinson's)
| Feature Name | Index | What It Measures | Formula | Parkinson's Effect |
|---|---|---|---|---|
spectral_centroid_mean |
64 | "Brightness" of sound | Σ(f×m)/Σ(m) | Lower (duller voice) |
spectral_centroid_std |
65 | Brightness variability | std(centroid) | More variable |
Physical Meaning: The "center of mass" of the frequency spectrum.
- High values = bright, sharp, high-pitched sounds
- Low values = dull, muffled, low-pitched sounds
Why it matters for Parkinson's:
- Parkinson's voices tend to be duller (lower centroid)
- Voice quality is less consistent (higher std)
| Feature Name | Index | What It Measures | Formula | Parkinson's Effect |
|---|---|---|---|---|
spectral_bandwidth_mean |
66 | Frequency spread | √(Σ( | f-μ |
spectral_bandwidth_std |
67 | Spread variability | std(bandwidth) | More variable |
Physical Meaning: How wide the frequency range is.
- High bandwidth = rich, full sound with many frequencies
- Low bandwidth = thin, narrow sound
Why it matters for Parkinson's:
- Parkinson's voices are often thinner (narrower bandwidth)
- Less harmonic richness
| Feature Name | Index | What It Measures | Formula | Parkinson's Effect |
|---|---|---|---|---|
spectral_rolloff_mean |
68 | Frequency containing 85% energy | f where Σ(m[f]) = 0.85×Σ(m) | Lower |
spectral_rolloff_std |
69 | Rolloff variability | std(rolloff) | More variable |
Physical Meaning: The frequency below which most energy is concentrated.
- High rolloff = significant high-frequency content
- Low rolloff = mostly low frequencies
Why it matters for Parkinson's:
- Parkinson's voices have less high-frequency energy
- Voice sounds muffled/dull
| Feature Name | Index | What It Measures | Formula | Parkinson's Effect |
|---|---|---|---|---|
zero_crossing_rate_mean |
70 | How often signal crosses zero | Σ(sign(x[t])≠sign(x[t-1]))/T | Higher (breathier) |
zero_crossing_rate_std |
71 | ZCR variability | std(zcr) | More variable |
Physical Meaning: Rate of sign changes in the audio signal.
- High ZCR = noisy, unvoiced sounds (like 's', 'sh')
- Low ZCR = tonal, voiced sounds (like 'a', 'o')
Why it matters for Parkinson's:
- Parkinson's voices are breathier (more noise = higher ZCR)
- Less clear phonation
def extract_features(self, y: np.ndarray) -> Optional[np.ndarray]:
"""
Extract all features from audio time series.
Args:
y: Audio time series (numpy array of amplitude values)
Returns:
Feature vector as 1D numpy array (72 values), or None if extraction fails
"""
try:
features = [] # List to collect all feature arrays
# ================================================================
# STEP 1: MFCC FEATURES (40 values: 20 mean + 20 std)
# ================================================================
mfccs = librosa.feature.mfcc(
y=y, # Audio time series
sr=self.sample_rate, # Sample rate (22050 Hz)
n_mfcc=self.n_mfcc # Number of coefficients (20)
)
# mfccs shape: (20, n_frames) - 20 coefficients for each time frame
features.append(np.mean(mfccs, axis=1)) # Shape: (20,) - mean of each coefficient
features.append(np.std(mfccs, axis=1)) # Shape: (20,) - std of each coefficient
# WHY MFCC?
# - Represents vocal tract shape
# - Parkinson's affects vocal tract control
# - Industry standard for voice/speech analysis
# ================================================================
# STEP 2: CHROMA FEATURES (24 values: 12 mean + 12 std)
# ================================================================
chroma = librosa.feature.chroma_stft(
y=y, # Audio time series
sr=self.sample_rate, # Sample rate
n_chroma=self.n_chroma # Number of pitch classes (12)
)
# chroma shape: (12, n_frames) - 12 pitch classes for each frame
features.append(np.mean(chroma, axis=1)) # Shape: (12,)
features.append(np.std(chroma, axis=1)) # Shape: (12,)
# WHY CHROMA?
# - Represents pitch class energy
# - Parkinson's causes monopitch (reduced pitch variation)
# - Chroma std directly measures pitch variability
# ================================================================
# STEP 3: SPECTRAL CENTROID (2 values: mean + std)
# ================================================================
spectral_centroid = librosa.feature.spectral_centroid(
y=y,
sr=self.sample_rate
)
# spectral_centroid shape: (1, n_frames)
features.append(np.mean(spectral_centroid)) # Scalar value
features.append(np.std(spectral_centroid)) # Scalar value
# WHY SPECTRAL CENTROID?
# - Measures "brightness" of sound
# - Parkinson's voices are typically duller (lower centroid)
# ================================================================
# STEP 4: SPECTRAL BANDWIDTH (2 values: mean + std)
# ================================================================
spectral_bandwidth = librosa.feature.spectral_bandwidth(
y=y,
sr=self.sample_rate
)
# spectral_bandwidth shape: (1, n_frames)
features.append(np.mean(spectral_bandwidth)) # Scalar
features.append(np.std(spectral_bandwidth)) # Scalar
# WHY SPECTRAL BANDWIDTH?
# - Measures frequency spread
# - Parkinson's voices are thinner (narrower bandwidth)
# ================================================================
# STEP 5: SPECTRAL ROLLOFF (2 values: mean + std)
# ================================================================
spectral_rolloff = librosa.feature.spectral_rolloff(
y=y,
sr=self.sample_rate
)
# spectral_rolloff shape: (1, n_frames)
features.append(np.mean(spectral_rolloff)) # Scalar
features.append(np.std(spectral_rolloff)) # Scalar
# WHY SPECTRAL ROLLOFF?
# - Frequency containing 85% of energy
# - Parkinson's has less high-frequency content
# ================================================================
# STEP 6: ZERO CROSSING RATE (2 values: mean + std)
# ================================================================
zcr = librosa.feature.zero_crossing_rate(y)
# zcr shape: (1, n_frames)
features.append(np.mean(zcr)) # Scalar
features.append(np.std(zcr)) # Scalar
# WHY ZCR?
# - Measures noisiness/breathiness
# - Parkinson's voices are breathier (higher ZCR)
# ================================================================
# STEP 7: FLATTEN ALL FEATURES INTO SINGLE VECTOR
# ================================================================
feature_vector = np.concatenate([
feat.flatten() if isinstance(feat, np.ndarray) else [feat]
for feat in features
])
# Final shape: (72,) - single 1D array with all features
# Validate feature vector length
expected_length = len(self.feature_names) # Should be 72
if len(feature_vector) != expected_length:
logger.error(f"Feature vector length mismatch")
return None
return feature_vector
except Exception as e:
logger.error(f"Error extracting features: {str(e)}")
return None| Parameter | Value | Why This Value? | Effect of Changing |
|---|---|---|---|
SAMPLE_RATE |
22050 Hz | Nyquist for voice (max freq ~8kHz) | Higher = more detail but larger files |
N_MFCC |
20 | Standard for speech analysis | More = finer detail, risk overfitting |
N_CHROMA |
12 | One per musical note | Fixed by music theory |
DURATION |
3 seconds | Sufficient for stable features | Longer = more stable, more processing |
Random Forest:
| Parameter | Value | Why? | Trade-off |
|---|---|---|---|
n_estimators |
100 | Good balance of accuracy/speed | More = better but slower |
max_depth |
10 | Prevents overfitting | Deeper = more complex, risk overfit |
random_state |
42 | Reproducibility | Any value works, 42 is conventional |
Logistic Regression:
| Parameter | Value | Why? | Trade-off |
|---|---|---|---|
max_iter |
1000 | Ensures convergence | More = slower but better convergence |
C |
1.0 | Balanced regularization | Lower = more regularization |
solver |
'lbfgs' | Fast for medium datasets | Others: 'liblinear', 'saga' |
SVM:
| Parameter | Value | Why? | Trade-off |
|---|---|---|---|
kernel |
'rbf' | Handles non-linear patterns | 'linear' faster but less powerful |
C |
1.0 | Balanced regularization | Higher = less regularization |
gamma |
'scale' | Auto-compute from data | 'auto' = 1/n_features |
| Parameter | Value | Why? |
|---|---|---|
TEST_SIZE |
0.15 (15%) | Enough test samples while keeping training data |
RANDOM_STATE |
42 | Reproducible splits |
stratify |
y | Maintain class balance in both sets |
| Parameter | Value | Why? |
|---|---|---|
PCA_VARIANCE |
0.95 (95%) | Retain most information while reducing dimensions |
svd_solver |
'full' | Exact decomposition for small-medium datasets |
What it does: Audio and music analysis library.
Why we use it:
- Built-in MFCC computation (would take 100+ lines to implement from scratch)
- Efficient FFT-based feature extraction
- Handles audio loading with resampling automatically
- Industry standard for audio ML
Alternatives considered:
torchaudio: Requires PyTorch, overkill for this projectpython_speech_features: Less maintained, fewer features
What it does: Numerical computing with arrays.
Why we use it:
- Audio signals ARE arrays - perfect match
- Vectorized operations (100x faster than Python loops)
- Required by all other ML libraries
We use it for:
- Storing audio samples
- Feature vectors
- All mathematical operations
What it does: Data manipulation and analysis.
Why we use it:
- DataFrame structure perfect for tabular feature data
- Easy CSV export for sharing with other tools
- Column names make debugging easier
We use it for:
- Storing features + labels together
features.csvfile format- Data exploration
What it does: Machine learning library.
Why we use it:
- All three models (RF, LR, SVM) in consistent API
- Built-in metrics (accuracy, precision, recall, F1)
- StandardScaler and PCA implementations
- Train/test split with stratification
Alternatives considered:
xgboost: More complex, not needed for this dataset sizekeras/tensorflow: Overkill, would require much more data
What it does: Visualization.
Why we use it:
- Confusion matrices need heatmaps (seaborn excels)
- Publication-quality plots
- Easy customization
We create:
- Confusion matrices
- Accuracy comparison bar charts
- Feature importance plots
- PCA variance scree plots
What it does: Serialization (saving/loading objects).
Why we use it:
- More efficient than pickle for numpy arrays
- Compresses large model files
- Native scikit-learn support
We save:
- Trained models (.joblib)
- Scalers (.joblib)
- PCA objects (.joblib)
What it does: Audio file I/O.
Why we use it:
- Reads/writes WAV files reliably
- Faster than librosa for file I/O
- Handles various encodings
What it does: Real-time audio recording.
Why we use it:
- Direct microphone access
- Low-latency recording
- Cross-platform (Windows, Mac, Linux)
Used in: predict.py --record feature
Why 72?
- MFCC: 20 coefficients is speech analysis standard
- Chroma: 12 pitch classes (fixed by music theory)
- Spectral: 4 features capture voice quality adequately
- ZCR: 1 feature sufficient for noisiness
Why not more?
- Diminishing returns (additional features add little information)
- Risk of overfitting increases
- Computation time increases
Why not fewer?
- Would miss important Parkinson's indicators
- Research shows these features are discriminative
Why both?
- Mean tells us what the typical value is
- Std tells us how stable that value is
- Parkinson's affects BOTH (altered values + increased variability)
Example:
- Healthy: MFCC mean = 100, std = 5 (stable)
- Parkinson's: MFCC mean = 80, std = 20 (different AND unstable)
Why train without PCA?
- Full feature interpretability (feature importance makes sense)
- No information loss
- Some models work well with original features
Why train with PCA?
- Faster prediction (fewer features)
- Removes multicollinearity
- May generalize better
Why both?
- Lets us compare which approach works better
- Different use cases (interpretability vs speed)
Why not 80/20?
- Dataset is small (~100 samples)
- Every training sample matters
- 15% test still gives ~15 samples per class
Why not 90/10?
- Need enough test samples for reliable evaluation
- 10% of 100 = only 10 test samples (too few)
Why Random Forest?
- Handles high-dimensional data well
- Provides feature importance
- Robust to outliers
- Non-linear patterns
Why Logistic Regression?
- Fast baseline
- Interpretable coefficients
- Less prone to overfitting
- Good for comparison
Why SVM?
- Excellent in high-dimensional spaces
- Kernel trick for non-linear patterns
- Often best for small-medium datasets
Why not neural networks?
- Dataset too small (~100 samples)
- Would require 1000+ samples minimum
- Overkill for this problem
Why not include in model?
- scikit-learn models don't include preprocessing
- Allows using same scaler with different models
- More flexible for experimentation
Critical: Must use SAME scaler/PCA from training during prediction!
| Error Location | Error Message | Cause | Fix |
|---|---|---|---|
load_audio() |
"Empty audio file" | File has no samples | Use valid WAV file |
load_audio() |
"Error loading..." | Corrupt file, wrong format | Convert to WAV |
extract_features() |
"Feature vector length mismatch" | Audio too short or corrupt | Use files >1 second |
DatasetProcessor.__init__() |
"Healthy directory not found" | Wrong path | Check data_dir |
| Error Location | Error Message | Cause | Fix |
|---|---|---|---|
load_data() |
FileNotFoundError | features.csv missing | Run feature_extraction.py first |
split_data() |
ValueError: stratify | Only one class present | Need both healthy and Parkinson's |
train_models() |
ConvergenceWarning | Model didn't converge | Increase max_iter |
| Error Location | Error Message | Cause | Fix |
|---|---|---|---|
validate_file() |
"File not found" | Wrong path | Check file path |
validate_file() |
"Unsupported file format" | Not a .wav file | Convert to WAV |
validate_file() |
"File is empty" | 0-byte file | Use valid file |
extract_features() |
"Failed to extract" | Corrupt audio | Re-record file |
load_scaler() |
"Scaler not found" | Training not completed | Run train.py first |
| Operation | Time (per file) | Total for 100 files |
|---|---|---|
| Load audio | ~50ms | 5 seconds |
| Extract MFCC | ~100ms | 10 seconds |
| Extract Chroma | ~30ms | 3 seconds |
| Extract spectral | ~20ms | 2 seconds |
| Save CSV | ~100ms | 10 seconds |
| Total | ~300ms | ~30 seconds |
| Model | Training Time |
|---|---|
| Random Forest | ~2 seconds |
| Logistic Regression | ~0.5 seconds |
| SVM | ~5 seconds |
| Operation | Time |
|---|---|
| Load audio | ~50ms |
| Extract features | ~200ms |
| Scale + PCA | ~5ms |
| Predict | ~1ms |
| Total | ~256ms |
| Component | Memory |
|---|---|
| Audio file (3 sec) | ~265 KB |
| Feature vector | ~576 bytes (72 × 8 bytes) |
| Random Forest model | ~3.5 MB |
| SVM model | ~2 MB |
| Scaler | ~2 KB |
| PCA | ~30 KB |
RAW AUDIO:
Shape: (n_samples,) where n_samples = 22050 × duration
Example: 3 seconds → (66150,)
Dtype: float32
AFTER LIBROSA.LOAD:
y: (66150,) audio time series
sr: 22050 sample rate
MFCC:
Shape: (n_mfcc, n_frames) = (20, ~259)
n_frames depends on audio length
CHROMA:
Shape: (n_chroma, n_frames) = (12, ~259)
SPECTRAL FEATURES:
Each: (1, n_frames) = (1, ~259)
AFTER np.mean/np.std:
MFCC mean: (20,)
MFCC std: (20,)
Chroma mean: (12,)
Chroma std: (12,)
Spectral (each): scalar
FINAL FEATURE VECTOR:
Shape: (72,)
Dtype: float64
FEATURES DATAFRAME:
Shape: (n_samples, 75) # 72 features + label + filename + filepath
AFTER TRAIN-TEST SPLIT:
X_train: (n_train, 72)
X_test: (n_test, 72)
y_train: (n_train,)
y_test: (n_test,)
AFTER STANDARDSCALER:
Same shapes, but values normalized (mean=0, std=1)
AFTER PCA:
X_train_pca: (n_train, n_components) where n_components ≈ 40-50
X_test_pca: (n_test, n_components)
MODEL PREDICTION:
Input: (1, n_features) for single sample
Output: (1,) for predict(), (1, 2) for predict_proba()
This project implements a complete machine learning pipeline for Parkinson's disease detection from voice audio. Here's what we built:
- Data Generation (
generate_sample_data.py): Synthetic voice recordings mimicking healthy and Parkinson's voices - Feature Extraction (
feature_extraction.py): 72 acoustic features from each audio file - Model Training (
train.py,pca_train.py): Three classifiers with and without PCA - Prediction (
predict.py): Terminal-based prediction with recording support - Utilities (
utils.py): Metrics, plotting, model saving
- 72 features extracted using audio signal processing
- 3 model types trained and compared
- ~86% accuracy achieved with SVM
- Production-ready pipeline with saved models
- Real-time prediction from microphone
| Category | Technologies |
|---|---|
| Audio Processing | librosa, soundfile, PyAudio |
| Numerical Computing | numpy |
| Data Handling | pandas |
| Machine Learning | scikit-learn |
| Visualization | matplotlib, seaborn |
| Serialization | joblib |
This system demonstrates that voice analysis can detect Parkinson's with meaningful accuracy. In production, with real patient data, this could become a non-invasive screening tool for early detection.
Complete documentation for educational purposes. For medical applications, consult healthcare professionals and conduct clinical trials.