Sneha had trained a model in Lesson 7 that predicted whether a loan application would default. Accuracy: 87%. She was proud of it. But then her friend Rohan asked: "Can I use your model from my phone app?" She sent him the .pkl file and Python code. He didn't know Python. Dead end.
Her teacher explained: "A trained model living in a Jupyter notebook is like a doctor who only sees patients at their own home. To be useful, they need a clinic โ a fixed address where anyone can walk in with their questions and receive answers. For ML models, that clinic is called a REST API. FastAPI lets you build one in under 50 lines of Python."
REST (Representational State Transfer) is a standard way for different programs to communicate over the internet. An API (Application Programming Interface) is the "front desk" of your ML model:
FastAPI is the most popular Python web framework for ML APIs because it is fast, automatically generates interactive documentation, and uses Pydantic for input validation โ catching bad data before it reaches your model.
income: float, FastAPI will automatically return a clear error if a client sends "income": "banana" โ before your ML model ever sees the bad input.
# โโ FILE 1: train_and_save.py โโ
# Run this first to create the model file
import pickle, numpy as np, pandas as pd
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.impute import SimpleImputer
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import accuracy_score
X, y = make_classification(
n_samples=1000, n_features=5, n_informative=4, random_state=42
)
feature_names = ["income", "loan_amount", "credit_score", "employment_years", "existing_loans"]
X_df = pd.DataFrame(X, columns=feature_names)
X_train, X_test, y_train, y_test = train_test_split(X_df, y, test_size=0.2, random_state=42)
pipeline = Pipeline([
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler()),
('model', GradientBoostingClassifier(n_estimators=100, random_state=42))
])
pipeline.fit(X_train, y_train)
print(f"Test accuracy: {accuracy_score(y_test, pipeline.predict(X_test)):.3f}")
with open("loan_model.pkl", "wb") as f:
pickle.dump(pipeline, f)
print("Model saved to loan_model.pkl")# โโ FILE 2: main.py โโ (the FastAPI application)
# Run with: uvicorn main:app --reload
import pickle
import numpy as np
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field, field_validator
# โโ Load model at startup (not on each request) โโ
with open("loan_model.pkl", "rb") as f:
pipeline = pickle.load(f)
app = FastAPI(
title="Loan Default Prediction API",
description="Predict probability of loan default. Built with FastAPI + scikit-learn.",
version="1.0.0"
)
# โโ Input schema: Pydantic validates every incoming request โโ
class LoanApplication(BaseModel):
income: float = Field(..., gt=0, description="Monthly income in thousands")
loan_amount: float = Field(..., gt=0, description="Loan amount in thousands")
credit_score: float = Field(..., ge=300, le=900, description="CIBIL score 300-900")
employment_years: float = Field(..., ge=0, description="Years of employment")
existing_loans: float = Field(..., ge=0, description="Number of existing loans")
@field_validator('credit_score')
@classmethod
def score_must_be_valid(cls, v):
if v < 300 or v > 900:
raise ValueError("CIBIL credit score must be between 300 and 900")
return v
class Config:
json_schema_extra = {
"example": {
"income": 45.0,
"loan_amount": 200.0,
"credit_score": 720.0,
"employment_years": 3.5,
"existing_loans": 1.0
}
}
# โโ Output schema โโ
class PredictionResponse(BaseModel):
prediction: int # 0 = no default, 1 = default
prediction_label: str # human-readable
default_probability: float # 0.0 โ 1.0
risk_level: str # Low / Medium / High
# โโ Endpoints โโ
@app.get("/")
def root():
return {"message": "Loan Default Prediction API is running",
"docs": "/docs", "version": "1.0.0"}
@app.get("/health")
def health():
return {"status": "ok", "model_loaded": pipeline is not None}
@app.post("/predict", response_model=PredictionResponse)
def predict(application: LoanApplication):
"""
Predict whether a loan application will default.
Returns prediction (0/1), probability, and risk level.
"""
try:
features = [[
application.income,
application.loan_amount,
application.credit_score,
application.employment_years,
application.existing_loans
]]
prediction = int(pipeline.predict(features)[0])
probability = float(pipeline.predict_proba(features)[0][1])
if probability < 0.25:
risk = "Low"
elif probability < 0.60:
risk = "Medium"
else:
risk = "High"
return PredictionResponse(
prediction=prediction,
prediction_label="Default" if prediction == 1 else "No Default",
default_probability=round(probability, 4),
risk_level=risk
)
except Exception as e:
raise HTTPException(status_code=500, detail=f"Prediction error: {str(e)}")# โโ FILE 3: test_api.py โโ (test the running API)
import requests
BASE = "http://127.0.0.1:8000"
# Test 1: Health check
resp = requests.get(f"{BASE}/health")
print("Health:", resp.json())
# Test 2: Low-risk applicant
low_risk = {
"income": 80.0,
"loan_amount": 150.0,
"credit_score": 800.0,
"employment_years": 10.0,
"existing_loans": 0.0
}
resp = requests.post(f"{BASE}/predict", json=low_risk)
print("Low-risk:", resp.json())
# Expected: {"prediction": 0, "prediction_label": "No Default",
# "default_probability": 0.04, "risk_level": "Low"}
# Test 3: High-risk applicant
high_risk = {
"income": 15.0,
"loan_amount": 500.0,
"credit_score": 380.0,
"employment_years": 0.5,
"existing_loans": 4.0
}
resp = requests.post(f"{BASE}/predict", json=high_risk)
print("High-risk:", resp.json())
# Expected: {"prediction": 1, "prediction_label": "Default",
# "default_probability": 0.87, "risk_level": "High"}
# Test 4: Invalid input (Pydantic validation)
bad_input = {
"income": -100, # negative โ Field gt=0 will reject
"loan_amount": 200.0,
"credit_score": 720.0,
"employment_years": 3.0,
"existing_loans": 1.0
}
resp = requests.post(f"{BASE}/predict", json=bad_input)
print("Bad input response:", resp.status_code, resp.json()["detail"][0]["msg"])http://127.0.0.1:8000/docs in your browser. FastAPI auto-generates an interactive UI where you can test your endpoints without writing any test code. Your model has a professional API with zero extra work.
!pip install fastapi uvicorn[standard] pyngrok requests โ start uvicorn as a background process โ use pyngrok to expose port 8000 as a public URL. Then share the ngrok URL with classmates who can call your API from their phones!