This project involves building a complete, end-to-end binary text classification system to classify movie reviews as either Positive or Negative. Unlike standard projects that rely solely on libraries, this project focuses on the mathematical implementation of Support Vector Machines (SVM) from scratch. We implement the core optimization algorithms, gradients, and loss functions manually using NumPy to understand the underlying mechanics of machine learning. The project culminates in a robust cloud deployment where the trained model is served as a REST API using FastAPI on GitHub Codespaces, demonstrating a full machine learning lifecycle from algorithm design to production deployment.
We utilize the IMDb Large Movie Review Dataset, a standard benchmark for binary sentiment classification.
- Source: Stanford AI Lab
- Total Samples: 50,000 reviews.
- Training Set: 40,000 samples (80%)
- Test Set: 10,000 samples (20%)
- Classes: Binary (Positive vs. Negative).
- Features: 20,000 (TF-IDF Vectors).
Before training, the raw text data undergoes several processing steps:
- HTML Tag Removal: Cleaning
<br />tags and other HTML artifacts. - Tokenization: Splitting text into individual words.
- N-grams: We use Bigrams (pairs of consecutive words) in addition to unigrams. This captures context (e.g., "not good" is different from "good").
- Example: "not good" -> ["not", "good", "not good"]
- TF-IDF Vectorization: We convert text into numerical vectors using Term Frequency-Inverse Document Frequency.
- Max Features: Limited to the top 20,000 most frequent n-grams to balance performance and memory.
- Stop Words: We explicitly kept stop words (like "not", "no") because they are critical for sentiment analysis.
Objective: Download, clean, and format the data for machine learning.
- We implemented a script
scripts/prepare_dataset.pythat automatically downloads the 80MB dataset. - It uses
scikit-learn'sTfidfVectorizerto transform raw text into a sparse matrix of size(50000, 20000). - The data is saved as
.joblibfiles (train_data.joblib,test_data.joblib) for fast loading during training.
Objective: Write a custom SVM class without using sklearn.svm.
-
File:
scripts/models.py -
Model Parameters:
-
Weights (
$w$ ): A vector of size 20,000. Initialized using a random normal distribution with small standard deviation ($0.01$ ) to ensure stable starting loss. -
Bias (
$b$ ): A scalar intercept term.
-
Weights (
- Score Calculation: The linear decision boundary is calculated as: $$ f(x) = w \cdot x + b $$
-
Optimization Algorithm: We implemented the Adam Optimizer (Adaptive Moment Estimation) manually.
- Unlike standard SGD, Adam adapts the learning rate for each parameter, leading to much faster convergence on sparse text data.
-
Hyperparameters: Learning Rate (
$\alpha = 0.001$ ), Regularization ($\lambda = 1e-6$ ), Batch Size ($256$ ).
Objective: Implement and analyze three different loss functions to guide the gradient descent.
-
Hinge Loss:
-
Formula:
$L = \max(0, 1 - y \cdot f(x))$ - Description: The standard loss for SVM. It penalizes predictions only if they are on the wrong side of the margin. It is robust to outliers but not differentiable at exactly 1.
-
Formula:
-
Squared Hinge Loss:
-
Formula:
$L = \max(0, 1 - y \cdot f(x))^2$ - Description: A smoother version of Hinge loss. It penalizes large errors more severely (quadratically). In our experiments, this often converged faster and achieved higher accuracy.
-
Formula:
-
Logistic Loss:
-
Formula:
$L = \log(1 + e^{-y \cdot f(x)})$ - Description: Technically makes the model a Logistic Regression classifier. It provides a smooth, probabilistic loss curve that is differentiable everywhere.
-
Formula:
Comparison Results:
- Squared Hinge generally performed best for this dataset, achieving ~90.6% accuracy.
- Hinge was close behind but slightly less stable during early epochs.
Objective: Establish a baseline using a production-grade library (scikit-learn).
- File:
scripts/library_svm.py - Models Implemented:
- Hinge Loss:
LinearSVC(loss='hinge') - Squared Hinge Loss:
LinearSVC(loss='squared_hinge') - Logistic Loss:
LogisticRegression()
- Hinge Loss:
- Performance: The library models achieved ~90.2% accuracy, serving as a strong benchmark for our manual implementation.
- Conclusion: Our manual implementation (90.6%) successfully matched and even slightly exceeded the library baseline.
We utilized GitHub Codespaces as our cloud development environment.
- Environment: Ubuntu Linux container.
- Dependencies: Python 3.10, NumPy, Pandas, Scikit-learn, FastAPI, Uvicorn.
- Setup: The environment is defined in
requirements.txtand automatically provisioned.
We executed the training pipeline entirely in the cloud.
- Training Script:
scripts/manual_svm.py - Compute: The training runs on the cloud VM's CPU.
- Artifacts: The best performing model is serialized and saved as
best_manual_svm.joblib(approx. 470KB).
Objective: Serve the trained model so it can be accessed over the web.
- Framework: FastAPI (a modern, fast web framework for building APIs).
- Server: Uvicorn (an ASGI web server implementation).
- Endpoint:
POST /predict- Accepts a JSON payload:
{"text": "Review content..."} - Returns:
{"label": "Positive"}or{"label": "Negative"}
- Accepts a JSON payload:
- Logic:
- Receives text.
- Loads the saved
vectorizer.joblib. - Transforms text to vectors.
- Loads
best_manual_svm.joblib(or library model). - Computes dot product score.
- Returns label based on sign of score.
Clone the repository and install the required Python packages:
git clone https://github.com/abrar898/Manual-SVM-Text-Classification-with-Loss-Function-Analysis.git
cd Manual-SVM-Text-Classification-with-Loss-Function-Analysis
pip install -r requirements.txtDownload and preprocess the IMDb dataset:
python3 scripts/prepare_dataset.py --out data/Train the Manual SVM model (this may take 1-2 minutes):
python3 scripts/manual_svm.py --epochs 40 --lambda_param 0.000001 --save best_manual_svm.joblibTrain the Library Baseline (Squared Hinge):
python3 scripts/library_svm.py --loss squared_hinge --save library_svm.joblib(You can also use --loss hinge or --loss logistic)
Start the web server:
uvicorn app.api:app --host 0.0.0.0 --port 8000Test the API (in a new terminal):
curl -X POST "http://localhost:8000/predict" \
-H "Content-Type: application/json" \
-d '{"text": "This movie was absolutely amazing and I loved it."}'