muhammadhabibna's picture
Upload folder using huggingface_hub
7b6dcff verified
Raw
History Blame Contribute Delete
5.2 kB
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import Dict, List, Any
import pandas as pd
import numpy as np
import pickle
import json
import shap
import os
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI(title="Fisherman Response Prediction API")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Load configuration and models
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
try:
with open(os.path.join(BASE_DIR, "feature_cols_eng.json"), "r") as f:
FEATURE_COLS = json.load(f)
with open(os.path.join(BASE_DIR, "target_cols_A.json"), "r") as f:
TARGET_COLS_A = json.load(f)
with open(os.path.join(BASE_DIR, "target_cols_B.json"), "r") as f:
TARGET_COLS_B = json.load(f)
with open(os.path.join(BASE_DIR, "modelA_catboost.pkl"), "rb") as f:
model_A = pickle.load(f)
with open(os.path.join(BASE_DIR, "modelB_xgboost.pkl"), "rb") as f:
model_B = pickle.load(f)
# Load background data for SHAP
df_train = pd.read_csv(os.path.join(BASE_DIR, "X_train_eng.csv"))
# CatBoost explainer uses a sample
bg_data_A = df_train.sample(50, random_state=42)
bg_data_B = df_train.sample(50, random_state=42)
except Exception as e:
print(f"Error loading models/data: {e}")
# Still allow app to start, but it will fail on /predict
class PredictRequest(BaseModel):
features: Dict[str, float]
def get_shap_factors(model, feature_names, X_input, bg_data, target_cols, top_n=5):
"""
Menghitung SHAP values dinamis untuk model ClassifierChain.
Karena base estimatornya dilatih dengan augmented features,
kita juga harus memberikan padding nol untuk background dan test set.
"""
n_base = len(feature_names)
X_input_np = X_input.values
# Rata-ratakan nilai absolute SHAP lintas semua base_estimator/label
total_shap_abs = np.zeros(n_base)
try:
for i, estimator in enumerate(model.estimators_):
# Augment background and input data with zeros representing previous labels
bg_aug = np.hstack([bg_data.values, np.zeros((len(bg_data), i))]) if i > 0 else bg_data.values
X_aug = np.hstack([X_input_np, np.zeros((1, i))]) if i > 0 else X_input_np
explainer = shap.TreeExplainer(estimator, bg_aug)
sv = explainer.shap_values(X_aug)
# xgboost tree explainer might return a list [sv_class0, sv_class1], we want class 1
if isinstance(sv, list):
sv = sv[1]
# Keep only the original base features (discard augmented feature SHAP values)
base_sv = sv[0, :n_base]
# Kita gabungkan pengaruh absolute-nya
total_shap_abs += np.abs(base_sv)
# Dapatkan index Top N
top_idx = np.argsort(total_shap_abs)[-top_n:][::-1]
top_factors = [
{"feature": feature_names[i], "importance": float(total_shap_abs[i])}
for i in top_idx if total_shap_abs[i] > 0
]
return top_factors
except Exception as e:
print(f"SHAP error: {e}")
return []
@app.post("/predict")
def predict(request: PredictRequest):
# Ensure all required features are present
input_dict = request.features
# Fill missing features with 0
row = {col: input_dict.get(col, 0.0) for col in FEATURE_COLS}
# Create DataFrame (1 row)
X_df = pd.DataFrame([row])
# --- MODEL A PREDICTION ---
probas_A = model_A.predict_proba(X_df)[0]
best_idx_A = np.argmax(probas_A)
cat_A = TARGET_COLS_A[best_idx_A]
prob_dict_A = {TARGET_COLS_A[i]: float(probas_A[i]) for i in range(len(TARGET_COLS_A))}
# --- MODEL B PREDICTION ---
probas_B = model_B.predict_proba(X_df)[0]
# Get Top 5 Actions
top_indices_B = np.argsort(probas_B)[-5:][::-1]
top_5_actions = [
{"action": TARGET_COLS_B[i].replace("_", " "), "probability": float(probas_B[i])}
for i in top_indices_B
]
# --- EXPLAINABILITY (SHAP) ---
top_factors = get_shap_factors(model_A, FEATURE_COLS, X_df, bg_data_A, TARGET_COLS_A, top_n=5)
# --- PERSONALIZED MESSAGE ---
lik_trust = input_dict.get("Kepercayaan LIK (1-5)", 3.0)
trust_level = "Tinggi" if lik_trust >= 4.0 else "Sedang" if lik_trust >= 3.0 else "Rendah"
message = (
f"Peringatan dinamis. Anda memiliki kepercayaan LIK yang {trust_level} ({lik_trust}). "
"Disarankan untuk memperhatikan tanda alam yang biasa Anda gunakan. "
)
if top_5_actions:
message += f"Sangat disarankan untuk: {top_5_actions[0]['action']}."
return {
"model_A": {
"predicted_category": cat_A.replace("_", " "),
"probabilities": prob_dict_A
},
"model_B": {
"top5_actions": top_5_actions
},
"explanation": {
"top_factors": top_factors
},
"message": message
}