🔗 Live demo — deployed on AWS Elastic Beanstalk ·
An end-to-end regression ML application that predicts a student's math score from demographic and academic features. It pairs a clean, modular training pipeline with MLflow experiment tracking, an automated test + CI workflow, a Dockerized Flask service (HTML form + JSON API), and an AWS Elastic Beanstalk deployment.
The project demonstrates a realistic ML workflow: ingest structured student data, build a reusable preprocessing pipeline, tune and compare several regression models with cross-validation, track every experiment in MLflow, serialize the winning model, and serve it behind a Flask web app and JSON API. Training is fully separated from inference so the deployed service stays lightweight.
- Regression-based score prediction from gender, race/ethnicity, parental education, lunch type, test-prep course, and reading/writing scores.
- Reusable preprocessing pipeline — median imputation + standard scaling for numeric features, most-frequent imputation + one-hot encoding for categoricals (
ColumnTransformer), serialized aspreprocessor.pkl. - Model selection done right — five regressors tuned with
GridSearchCV, the winner chosen by cross-validated R² (never by peeking at the test set), with train/test R² tracked to surface over-fitting. - MLflow experiment tracking — every model's params and cv/train/test R² are logged and comparable in the MLflow UI.
- Web app + JSON API — a Flask form (
/predictdata) with input validation and a JSON endpoint (POST /predict) for programmatic use, served by Gunicorn. - Tested & linted in CI —
pytest+ruffrun on every push/PR via GitHub Actions. - Containerized — a
Dockerfileruns the whole service anywhere. - Modular pipeline — Data Ingestion → Data Transformation → Model Training → Prediction, with custom logging and exceptions.
Five regressors were tuned with GridSearchCV (cv=3) on an 80/20 split of 1,000 records. The winner is selected by cross-validated R²; test R² is reported once for the selected model.
| Model | CV R² | Train R² | Test R² |
|---|---|---|---|
| Linear Regression ✅ | 0.865 | 0.874 | 0.880 |
| Gradient Boosting | 0.852 | 0.894 | 0.873 |
| Random Forest | 0.832 | 0.976 | 0.852 |
| AdaBoost | 0.826 | 0.847 | 0.848 |
| Decision Tree | 0.713 | 1.000 | 0.729 |
Selected model: Linear Regression — it has the best cross-validated score and generalizes best (test R² 0.880). Note how Decision Tree (train 1.000 / test 0.729) and Random Forest (train 0.976) memorize the training data — exactly the over-fitting that CV-based selection guards against.
Reproduce these numbers with python -m src.pipeline.train_pipeline and inspect the runs with mlflow ui.
Student-ML-Project/
├── .ebextensions/
│ └── python.config
├── .github/workflows/
│ └── ci.yml # Lint (ruff) + tests (pytest) on push/PR
├── Notebook/
│ ├── Dataset/Student.csv
│ ├── EDA Student Performance.ipynb
│ └── Model Training.ipynb
├── artifacts/ # Generated: data splits, model.pkl, preprocessor.pkl
├── src/
│ ├── components/
│ │ ├── data_ingestion.py
│ │ ├── data_transformation.py
│ │ └── model_trainer.py # GridSearchCV + CV selection + MLflow logging
│ ├── pipeline/
│ │ ├── predict_pipeline.py
│ │ └── train_pipeline.py
│ ├── exception.py
│ ├── logger.py
│ └── utils.py
├── templates/ # home.html, index.html
├── tests/ # pytest: CustomData + Flask app/API
├── app.py # Flask app: HTML form + JSON API + validation
├── application.py # WSGI entrypoint (Elastic Beanstalk)
├── Dockerfile / .dockerignore
├── conftest.py / ruff.toml
├── Procfile / buildspec.yml
├── requirements.txt # Runtime dependencies
└── requirements-dev.txt # Training/CI deps (mlflow, pytest, ruff)
Python 3.11 · Flask + Gunicorn · scikit-learn · Pandas/NumPy · MLflow · pytest + ruff · Docker · GitHub Actions · AWS Elastic Beanstalk / CodePipeline / CodeBuild
# 1. Setup
python -m venv venv && venv\Scripts\activate # (Linux/macOS: source venv/bin/activate)
pip install -r requirements.txt -r requirements-dev.txt
# 2. (Optional) Retrain — writes artifacts/ and logs runs to MLflow
python -m src.pipeline.train_pipeline
mlflow ui # view experiments at http://localhost:5000
# 3. Run the web app
python app.py # http://localhost:5000curl -X POST http://localhost:5000/predict \
-H "Content-Type: application/json" \
-d '{"gender":"male","race_ethnicity":"group C","parental_level_of_education":"some college","lunch":"standard","test_preparation_course":"completed","reading_score":74,"writing_score":71}'
# -> {"math_score": 74.69}Invalid input returns a 400 with a clear error message instead of leaking a stack trace.
ruff check .
pytest -qdocker build -t student-marks-predictor .
docker run -p 5000:5000 student-marks-predictorThe app runs live on AWS Elastic Beanstalk (single-instance t3.micro, Python 3.11):
→ student-marks-predictorweb.us-east-1.elasticbeanstalk.com
Gunicorn serves the WSGI app (Procfile → gunicorn application:application), with the platform configured via .ebextensions/. The repo also includes a CodeBuild spec (buildspec.yml) for a GitHub → CodeBuild → Elastic Beanstalk pipeline, so continuous deployment can be re-enabled at any time. On the code side, a GitHub Actions workflow (ci.yml) lints and tests every push.
Deploy an update manually: build a clean source bundle from the committed code and upload it in the EB console:
git archive -o ../student-marks.zip HEAD # zip only tracked files, at the archive root
# EB console → environment → Upload and deploy → choose the zip → set a version label → Deployℹ️ The demo may occasionally be offline if the environment is paused to avoid cloud costs. The app runs identically via
python app.pyor Docker.
Prakhar Srivastava — Aspiring Data Scientist & ML Engineer | Machine Learning, Deep Learning & Generative AI
