A machine learning system that classifies/detects SMS spam messages using TF-IDF vectorization using logistic regression (scikit-learn) with ~96.42% accuracy, exposes predictions via a FastAPI JSON API, and explains why the model made each decision using interpretable feature contributions. You can interact with the API from simple CLI utilities.
The model is trained on the UCI SMS Spam Collection dataset and is saved as artifacts/model.joblib. The dataset contains 5,574 SMS messages labeled as ham (not spam) or spam, stored as tab-separated lines with two columns: label and text (the message content). This project maps ham to 0 and spam to 1, then trains and evaluates using a stratified train/test split.
Spam classification using:
- TF-IDF vectorization
- Logistic Regression (scikit-learn)
Model explainability:
- Shows which words influenced the prediction
- Percent-based contribution for interpretability
FastAPI backend:
- /predict endpoint for real-time inference
Human-friendly CLI client:
- Pretty-printed predictions and explanations
Pytest test suite:
- Model tests
- Prediction logic tests
- API endpoint tests
Dockerized:
- One-command containerized deployment
API Response:
{
"message": "free phone now",
"prediction": "spam",
"confidence": 52.515,
"explanation": [
{
"word": "free",
"direction": "spam",
"percent": 79.99
},
{
"word": "phone",
"direction": "spam",
"percent": 20.01
}
]
}"free phone now"
Prediction: SPAM
Confidence: 52.515%
Why? (Percentages show relative contributions of words to the model's decision, not absolute probability)
• "free" increased spam likelihood by 79.99%
• "phone" increased spam likelihood by 20.01%ML-Spam-Detector-API/
├── app/ # FastAPI application
│ ├── main.py # API entry point with `/predict` endpoint
│ └── schemas.py # Pydantic request/response models
├── model/ # ML model training & evaluation
│ ├── dataset.py # Dataset download + loading helpers
│ ├── train.py # Trains/saves TF-IDF + Logistic Regression model
│ ├── evaluate.py # Evaluates the saved model on the test split
│ ├── predict.py # Prediction + explanation helpers (used by API)
│ ├── load_model.py # Helper function to load the model
│ └── try_predict.py # Example batch predictions using the saved model
├── utils/ # CLI utilities
│ ├── call_api.py # Simple interactive API CLI client.
│ └── call_api_pretty.py # Pretty-printed interactive API CLI client.
├── tests/ # Pytest test suite for API, model loading, and predictions
│ ├── test_api.py
│ ├── test_model.py
│ └── test_predict.py
├── artifacts/ # Contains saved model
│ └── model.joblib
├── data/ # Contains UCI SMS Spam Collection dataset
│ └── SMSSpamCollection
├── Dockerfile
├── requirements.txt
└── README.mdMachine Learning / Data Processing
- Pandas (data loading, preprocessing, exploration)
- scikit-learn (TF-IDF vectorization, Logistic Regression)
Backend
- FastAPI
- Pydantic
- Uvicorn
DevOps / Tooling
- Docker
- Pytest
- Requests
From the current evaluation run on the held-out test split:
Classification Report:
precision recall f1-score support
0 0.9603 1.0000 0.9798 242
1 1.0000 0.7297 0.8438 37
accuracy 0.9642 279
macro avg 0.9802 0.8649 0.9118 279
weighted avg 0.9656 0.9642 0.9617 279
Confusion Matrix:
[[242 0]
[ 10 27]]
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtpython model/train.pyThis downloads the dataset to data/SMSSpamCollection (if missing),
trains the model, and saves it to artifacts/model.joblib.
python model/evaluate.pyEvaluates on the held-out test split and prints the classification report and confusion matrix.
uvicorn app.main:app --reloadExample request:
curl -X POST http://127.0.0.1:8000/predict \
-H "Content-Type: application/json" \
-d '{"message": "Free entry in 2 a wkly comp"}'Response fields:
message: original inputprediction:spamornot_spamconfidence: percentage probabilityexplanation: top contributing words and their impact
Note: Instead of running step 4 locally, you can run the service via the Docker section below.
After starting the API, in a separate terminal run:
python utils/call_api.pyOr the pretty-printed variant:
python utils/call_api_pretty.pyType messages directly into the terminal to see predictions and explanations.
Note: Example responses for both variants were shown above.
Build the image (make sure artifacts/model.joblib exists first):
docker build -t ml-spam-detector .Run the API container:
docker run --rm -p 8000:8000 ml-spam-detectorRun the full test suite:
pytestTests cover:
- Model loading
- Prediction correctness
- API endpoint behavior