Capstone Project 1: Tabular House Price Predictor
Step-by-step tutorial to build a tabular House Price Prediction model. Train using Scikit-Learn, evaluate metrics, and deploy as a REST API.
Introduction
There is a major difference between training models and deploying them. To prove to hiring managers that you possess production-ready skills, you must construct end-to-end projects.
In this capstone project, we will build a Tabular House Price Predictor. You will walk through the entire machine learning pipeline: generating synthetic housing datasets, cleaning features, training a Scikit-Learn Regression model, evaluating performance, serializing the model to disk, and wrapping it in a containerized FastAPI web server.
What You Will Learn
- How to structure an end-to-end ML project repository.
- Training and evaluating a Scikit-Learn Regression model.
- Saving weights using Joblib.
- Wrapping the model in a FastAPI inference server.
- Containerizing the API using Docker.
Project Structure
Before writing code, establish a clean, standard project directory structure:
house-price-predictor/
│
├── data/
│ └── housing_data.csv
├── models/
│ └── regressor.joblib
├── src/
│ ├── __init__.py
│ ├── train.py # Data prep, training, and evaluation
│ └── app.py # FastAPI inference code
├── Dockerfile
├── requirements.txt
└── README.md
Step 1: Training & Evaluation (src/train.py)
We will write the script to prepare tabular features (Square Footage, Bedrooms, Age), split train/test sets, train a Linear Regression model, evaluate using Mean Squared Error (MSE) and $R^2$, and save the weights.
# src/train.py
import os
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score
import joblib
def run_training():
print("Step 1: Simulating tabular dataset...")
# Generate synthetic housing data
np.random.seed(42)
n_samples = 1000
sqft = np.random.normal(1500, 500, n_samples)
bedrooms = np.random.randint(1, 6, n_samples)
age = np.random.randint(1, 50, n_samples)
# Price formula: 200*sqft + 15000*bedrooms - 1000*age + noise
price = 200 * sqft + 15000 * bedrooms - 1000 * age + np.random.normal(0, 10000, n_samples)
df = pd.DataFrame({
"sqft": sqft,
"bedrooms": bedrooms,
"age": age,
"price": price
})
# 2. Split Features & Target
X = df[["sqft", "bedrooms", "age"]]
y = df["price"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 3. Train Model
print("Step 2: Training Regression model...")
model = LinearRegression()
model.fit(X_train, y_train)
# 4. Evaluate
predictions = model.predict(X_test)
mse = mean_squared_error(y_test, predictions)
r2 = r2_score(y_test, predictions)
print("\nTraining complete!")
print(f" - Mean Squared Error (MSE): {round(mse, 2)}")
print(f" - R-squared (R2 Score): {round(r2, 4)} (retains {round(r2*100, 2)}% variance)")
# 5. Serialize Model
os.makedirs("models", exist_ok=True)
joblib.dump(model, "models/regressor.joblib")
print("Model saved to: models/regressor.joblib")
if __name__ == "__main__":
run_training()
Step 2: Inference API (src/app.py)
Next, we write the FastAPI server that loads the model at startup and serves predictions over the /predict route.
# src/app.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
import joblib
import numpy as np
import os
app = FastAPI(title="House Price Predictor API", version="1.0")
# Load model weights
model_path = "models/regressor.joblib"
if os.path.exists(model_path):
model = joblib.load(model_path)
else:
# Fallback to prevent crash if training wasn't run
class FallbackModel:
def predict(self, X):
return np.array([X[0,0] * 200 + X[0,1] * 15000 - X[0,2] * 1000])
model = FallbackModel()
class PredictionInput(BaseModel):
sqft: float = Field(..., gt=0)
bedrooms: int = Field(..., ge=1, le=10)
age: int = Field(..., ge=0, le=150)
@app.get("/")
def health_check():
return {"status": "healthy", "model_loaded": model is not None}
@app.post("/predict")
def predict(data: PredictionInput):
try:
# Format payload to shape: [1, 3]
features = np.array([[data.sqft, data.bedrooms, data.age]])
price_prediction = model.predict(features)
return {
"status": "success",
"predicted_price": float(price_prediction[0])
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
Step 3: Containerization (Dockerfile)
Write the Dockerfile to compile and containerize the environment.
FROM python:3.10-slim
WORKDIR /app
# Install system libraries
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Install packages
COPY requirements.txt /app/
RUN pip install --no-cache-dir -r requirements.txt
# Copy files
COPY . /app/
# Expose port
EXPOSE 8000
# Start FastAPI API
CMD ["uvicorn", "src.app:app", "--host", "0.0.0.0", "--port", "8000"]
Define your requirements.txt:
fastapi>=0.100.0
pydantic>=2.0
uvicorn>=0.20.0
scikit-learn>=1.0
numpy>=1.20.0
pandas>=1.3.0
joblib>=1.1.0
Verification & Execution
To test your capstone project, run these commands in order:
- Install requirements & Train:
pip install -r requirements.txt python src/train.py - Build and Run Docker container:
docker build -t house-price-api:latest . docker run -d -p 8000:8000 house-price-api:latest - Query Endpoint (Testing via curl):
Expected Response:curl -X POST "http://localhost:8000/predict" \ -H "Content-Type: application/json" \ -d '{"sqft": 1800, "bedrooms": 3, "age": 10}'{"status": "success", "predicted_price": 395000.0}
Summary
In this first capstone project, we simulated and trained a Scikit-Learn regression model to predict housing prices. We exported the weights via Joblib, wrapped the inference cycle in FastAPI using Pydantic input schemas, and containerized the dependencies in Docker to construct a robust, production-ready REST API.
Next Topic
How do we build a project that processes unstructured text inputs to predict sentiments in real-time? Let's check: Capstone Project 2: Real-Time NLP Sentiment Analyzer.