ML / app.py
shodhyadav's picture
Update app.py
ea666bc verified
Raw
History Blame Contribute Delete
5.83 kB
# app.py - Gradio app to serve a saved sklearn pipeline (loan_pipeline.pkl)
# Robust, commented, and production-friendly.
import joblib
import pandas as pd
import gradio as gr
import traceback
from typing import Tuple, Dict, Any
MODEL_PATH = "loan_pipeline.pkl" # file must be in the same repo root
# --------------------------
# Load model safely (with helpful error message)
# --------------------------
try:
model = joblib.load(MODEL_PATH)
except Exception as e:
# If the model fails to load (common: sklearn version mismatch),
# store the exception so the UI can display an informative error.
model = None
load_error = traceback.format_exc()
else:
load_error = None
# --------------------------
# Utility: safe conversion helper
# --------------------------
def safe_cast(value: Any, to_type, default=None):
"""
Try to cast `value` to `to_type`. If fails, return `default`.
Keeps app from crashing when users type bad input.
"""
try:
return to_type(value)
except Exception:
return default
# --------------------------
# Prediction function
# --------------------------
def predict_loan(age, income, employment, credit_score, dependents,
loan_amount, purpose, existing_debt, marital_status,
education_level, default_history) -> Tuple[str, Dict[str, float]]:
"""
Build one-row DataFrame from inputs, run model.predict & predict_proba,
then return (decision_text, probabilities_map).
Return value shape matches Gradio outputs: (Textbox, Label).
"""
# If model failed to load, return error message
if model is None:
# Return error text + empty label mapping
return ("ERROR: Model load failed. See server logs.", {"ERROR": 1.0})
# Coerce numeric inputs safely
age_i = safe_cast(age, int, None)
income_f = safe_cast(income, float, None)
credit_score_i = safe_cast(credit_score, int, None)
dependents_i = safe_cast(dependents, int, None)
loan_amount_f = safe_cast(loan_amount, float, None)
existing_debt_f = safe_cast(existing_debt, float, None)
default_history_i = safe_cast(default_history, int, None)
# Basic validation: ensure required numeric fields are present
missing_inputs = []
for name, val in [
("age", age_i), ("income", income_f), ("credit_score", credit_score_i),
("dependents", dependents_i), ("loan_amount", loan_amount_f),
("existing_debt", existing_debt_f), ("default_history", default_history_i)
]:
if val is None:
missing_inputs.append(name)
if missing_inputs:
message = f"Invalid or missing numeric inputs: {', '.join(missing_inputs)}"
# Return a user-friendly error message plus a label map so Gradio doesn't crash.
return (f"INPUT ERROR: {message}", {"ERROR": 1.0})
# Build the row dict exactly matching training column names
row = {
'age': age_i,
'income': income_f,
'employment': employment,
'credit_score': credit_score_i,
'dependents': dependents_i,
'loan_amount': loan_amount_f,
'purpose': purpose,
'existing_debt': existing_debt_f,
'marital_status': marital_status,
'education_level': education_level,
'default_history': default_history_i
}
# Create DataFrame (single-row) - pipeline expects DataFrame with same columns
try:
df = pd.DataFrame([row])
except Exception as e:
return (f"ERROR building DataFrame: {e}", {"ERROR": 1.0})
# Run model prediction inside try/except to catch runtime issues
try:
pred = int(model.predict(df)[0]) # 0 or 1
proba = model.predict_proba(df)[0] # [prob_no, prob_yes]
except Exception as e:
# If predict fails, return readable error
tb = traceback.format_exc()
return (f"PREDICTION ERROR: {str(e)}", {"ERROR": 1.0})
# Create human-friendly outputs
decision = "YES" if pred == 1 else "NO"
probs_map = {"NO": round(float(proba[0]), 4), "YES": round(float(proba[1]), 4)}
return (f"Decision: {decision}", probs_map)
# --------------------------
# Build Gradio UI
# --------------------------
title = "Loan Approval Demo"
description = (
"Enter applicant details and get a prediction from the saved pipeline.\n\n"
"If the model fails to load or predict, a helpful error message will appear here."
)
# Input widgets. Keep values and types aligned with training data.
inputs = [
gr.Number(label="age", value=30, precision=0),
gr.Number(label="income", value=50000.0),
gr.Dropdown(label="employment", choices=["employed","self-employed","unemployed","contract"], value="employed"),
gr.Number(label="credit_score", value=650, precision=0),
gr.Number(label="dependents", value=0, precision=0),
gr.Number(label="loan_amount", value=100000.0),
gr.Dropdown(label="purpose", choices=["home","car","education","personal","business"], value="personal"),
gr.Number(label="existing_debt", value=0.0),
gr.Dropdown(label="marital_status", choices=["single","married","divorced","widowed"], value="single"),
gr.Dropdown(label="education_level", choices=["other","highschool","bachelor","master","phd"], value="bachelor"),
gr.Dropdown(label="default_history", choices=[0,1], value=0)
]
# Outputs: Textbox for readable decision + Label for probabilities
outputs = [
gr.Textbox(label="Decision"),
gr.Label(num_top_classes=2, label="Probabilities")
]
iface = gr.Interface(
fn=predict_loan,
inputs=inputs,
outputs=outputs,
title=title,
description=description,
)
# --------------------------
# Launch the app (only when run as script)
# --------------------------
if __name__ == "__main__":
iface.launch()