End-to-end customer churn prediction system built on the IBM Telco dataset. Covers the full ML lifecycle: data preparation, multi-model experimentation tracked with MLflow, model registration, and a live REST inference API served with FastAPI.
telco_churn.csv
|
data_prep.py - cleaning, encoding, scaling, scaler persistence
|
train.py - trains 5 models, logs all runs to MLflow Tracking
|
MLflow UI - compare runs by AUC, F1, accuracy, recall
|
Model Registry - best model promoted to @production alias
|
serve.py - FastAPI loads @production model, exposes /predict
|
POST /predict - JSON in, churn probability + label out
TelcoChurnML/
|
+-- data/
| +-- telco_churn.csv # IBM Telco Customer Churn (Kaggle)
|
+-- src/
| +-- data_prep.py # preprocessing pipeline
| +-- train.py # MLflow experiment runner
| +-- serve.py # FastAPI inference server
|
+-- artifacts/
| +-- scaler.pkl # fitted StandardScaler (auto-generated)
| +-- cm_*.png # confusion matrix per run (auto-generated)
|
+-- notebooks/
| +-- 01_eda.ipynb # exploratory data analysis
|
+-- mlruns/ # MLflow tracking store (auto-generated)
+-- requirements.txt
+-- README.md
IBM Telco Customer Churn - Kaggle
- 7,043 customers, 21 features
- Target:
Churn(Yes/No) - 26.5% positive rate (class imbalance handled) - Features include tenure, contract type, payment method, internet service, monthly charges
| Model | AUC | F1 | Accuracy | Recall |
|---|---|---|---|---|
| Random Forest (200) | 0.865 | 0.660 | 0.793 | 0.759 |
| Logistic Regression | 0.862 | 0.638 | 0.751 | 0.828 |
| XGBoost (100) | 0.855 | 0.634 | 0.757 | 0.796 |
| XGBoost (200) | 0.854 | 0.638 | 0.759 | 0.802 |
| Random Forest (100) | 0.845 | 0.537 | 0.793 | 0.453 |
Winner: Random Forest (200) - highest AUC at 0.865, strong recall of 0.759 (catches 76% of actual churners). Registered as TelcoChurnModel@production in MLflow Model Registry.
Class imbalance handling:
- Logistic Regression + Random Forest:
class_weight="balanced" - XGBoost:
scale_pos_weight=3(ratio of negative/positive class)
Predicted
Not Churn Churn
Actual Not Churn 834 202
Churn 90 283
- True Positives (caught churners): 283
- False Negatives (missed churners): 90
- False Positives (wrong churn flags): 202
1. Clone the repo
git clone https://github.com/hamzapiracha/TelcoChurnML.git
cd TelcoChurnML2. Create virtual environment
python -m venv venv
venv\Scripts\activate # Windows
source venv/bin/activate # Mac/Linux3. Install dependencies
pip install -r requirements.txt4. Add dataset
Download telco_churn.csv from Kaggle and place it in data/.
Step 1 - Train all models and log to MLflow
python src/train.pyStep 2 - Open MLflow UI to compare runs
mlflow ui
# Open http://localhost:5000
# Sort by AUC descending to identify best model
# Register best model as TelcoChurnModel with alias @productionStep 3 - Start inference server
Open a second terminal (keep MLflow UI running in the first):
python src/serve.py
# Server starts at http://localhost:8000Accepts customer features as JSON, returns churn probability and label.
Request body:
{
"gender": 1,
"SeniorCitizen": 0,
"Partner": 0,
"Dependents": 0,
"tenure": 2,
"PhoneService": 1,
"MultipleLines": 0,
"OnlineSecurity": 0,
"OnlineBackup": 0,
"DeviceProtection": 0,
"TechSupport": 0,
"StreamingTV": 0,
"StreamingMovies": 0,
"PaperlessBilling": 1,
"MonthlyCharges": 70.0,
"TotalCharges": 140.0,
"InternetService_Fiber_optic": 1,
"InternetService_No": 0,
"Contract_One_year": 0,
"Contract_Two_year": 0,
"PaymentMethod_Credit_card_automatic": 0,
"PaymentMethod_Electronic_check": 1,
"PaymentMethod_Mailed_check": 0
}Response:
{
"churn_probability": 0.828,
"prediction": "Churn",
"confidence": "82.8%"
}Field encoding:
| Field | Encoding |
|---|---|
| gender | 1 = Male, 0 = Female |
| SeniorCitizen | 1 = Yes, 0 = No |
| All binary service fields | 1 = Yes, 0 = No / No service |
| InternetService_Fiber_optic | 1 if Fiber optic, else 0 |
| InternetService_No | 1 if No internet, else 0 |
| Contract_One_year | 1 if one-year contract, else 0 |
| Contract_Two_year | 1 if two-year contract, else 0 |
| PaymentMethod_* | 1 if that payment method, else 0 |
curl http://localhost:8000/health{
"status": "ok",
"model_loaded": true
}mlflow
fastapi
uvicorn
scikit-learn
xgboost
pandas
numpy
matplotlib
seaborn
pydantic
Add the following screenshots to a
screenshots/folder and link them here:
- MLflow runs table sorted by AUC
- Random Forest (200) confusion matrix
- Model Registry showing TelcoChurnModel@production
- curl /predict response in terminal
Why stratified train/test split?
With 26.5% churn rate, a random split risks imbalanced evaluation sets. stratify=y preserves the ratio in both splits — churn rate is 0.266 in train and 0.265 in test.
Why load the model once at startup?
Loading from MLflow Registry takes ~1-2 seconds. Loading on every request would make the API unusable under any real traffic. FastAPI's startup event loads both model and scaler into memory once — all requests share the same in-memory objects.
Why alias over stage in MLflow 3.x?
MLflow 3.12 deprecated the Staging/Production stage system. The @production alias is the current recommended pattern for marking a model version as live. Load path: models:/TelcoChurnModel@production.
Mohammad Hamza Piracha | Data Scientist & Applied AI Engineer | LinkedIn | hamzapiracha@live.com