sheltonmaharesh commited on
Commit
6ce8828
·
verified ·
1 Parent(s): 24d1985

Deploy backend Flask app

Browse files
Files changed (1) hide show
  1. bot_detector_api.py +62 -54
bot_detector_api.py CHANGED
@@ -1,25 +1,19 @@
1
  import numpy as np
2
- import pandas as pd
3
  import joblib
 
4
  import shap
5
- import matplotlib.pyplot as plt
6
- import base64
7
  from flask import Flask, request, jsonify
8
  from urllib.parse import urlparse, parse_qs
9
- from io import BytesIO
10
-
11
- # Initialize SHAP
12
- shap.initjs()
13
 
14
- # Initialize Flask app
15
  app = Flask("Bot detector")
16
 
17
- # Health check route
18
  @app.get('/')
19
  def home():
20
  return "✅ Welcome to the Bot Prediction API!"
21
 
22
- # Load models and transformers
23
  model = joblib.load("model.joblib")
24
  encoders = joblib.load("encoders.joblib")
25
  scaler = joblib.load("scaler.joblib")
@@ -27,8 +21,8 @@ if_model = joblib.load("best_if_model.joblib")
27
  svm_model = joblib.load("best_svm_model.joblib")
28
  feature_names = joblib.load("feature_names.joblib")
29
 
30
- # Instantiate SHAP explainer once
31
- explainer = shap.Explainer(model, feature_names=feature_names)
32
 
33
  def parse_url_params(url):
34
  try:
@@ -75,57 +69,71 @@ def prepare_features(row_dict):
75
 
76
  return df[feature_names]
77
 
78
- def get_shap_explanation(X_row, encoders=None, top_n=10, class_index=1):
79
- shap_vals = explainer(X_row)
80
- shap_value = shap_vals[0].values[:, class_index]
81
- base_val = shap_vals[0].base_values[class_index]
 
82
 
83
- decoded = {}
84
- for col in X_row.columns:
85
- val = X_row.iloc[0][col]
 
86
  try:
87
- decoded[col] = encoders[col].inverse_transform([int(val)])[0] if encoders and col in encoders else val
 
 
 
88
  except:
89
- decoded[col] = val
90
 
91
- contribs = list(zip(X_row.columns, [decoded[c] for c in X_row.columns], shap_value))
92
- contribs = sorted(contribs, key=lambda x: abs(x[2]), reverse=True)[:top_n]
 
93
 
94
- pos, neg = [], []
95
- for f, v, s in contribs:
96
- line = f"- {f:20} = {str(v):<15} ➤ impact: {abs(s):.4f}"
97
- (pos if s > 0 else neg).append(line)
98
 
99
- explanation = f"Base value: {base_val:.4f} → Final: {base_val + np.sum(shap_value):.4f}\n\n"
100
- if pos:
101
- explanation += "🔺 Positive Influences:\n" + "\n".join(pos) + "\n\n"
102
- if neg:
103
- explanation += "🔻 Negative Influences:\n" + "\n".join(neg)
 
104
 
105
- # Waterfall plot
106
- fig = plt.figure()
107
- shap.plots.waterfall(shap_vals[0][:, class_index], max_display=top_n, show=False)
108
- buf = BytesIO()
109
- plt.savefig(buf, format="png", bbox_inches='tight')
110
- plt.close(fig)
111
- buf.seek(0)
112
- image_base64 = base64.b64encode(buf.getvalue()).decode("utf-8")
113
 
114
- return explanation, image_base64
 
 
115
 
116
- @app.post('/v1/predict')
117
- def predict():
118
- row = request.get_json()
119
- X = prepare_features(row)
120
 
121
- pred_prob = model.predict_proba(X)[0][1]
122
- pred_label = int(pred_prob >= 0.5)
123
 
124
- explanation, img_base64 = get_shap_explanation(X, encoders=encoders)
 
125
 
126
- return jsonify({
127
- "Prediction": "Bot Attack" if pred_label else "Legitimate",
128
- "Bot Probability": round(float(pred_prob), 4),
129
- "SHAP Explanation": explanation,
130
- "SHAP Waterfall Plot": f"data:image/png;base64,{img_base64}"
131
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import numpy as np
 
2
  import joblib
3
+ import pandas as pd
4
  import shap
 
 
5
  from flask import Flask, request, jsonify
6
  from urllib.parse import urlparse, parse_qs
 
 
 
 
7
 
8
+ # Initialize Flask
9
  app = Flask("Bot detector")
10
 
11
+ # Health check
12
  @app.get('/')
13
  def home():
14
  return "✅ Welcome to the Bot Prediction API!"
15
 
16
+ # Load models and utilities
17
  model = joblib.load("model.joblib")
18
  encoders = joblib.load("encoders.joblib")
19
  scaler = joblib.load("scaler.joblib")
 
21
  svm_model = joblib.load("best_svm_model.joblib")
22
  feature_names = joblib.load("feature_names.joblib")
23
 
24
+ # SHAP explainer (tree-based models)
25
+ explainer = shap.TreeExplainer(model)
26
 
27
  def parse_url_params(url):
28
  try:
 
69
 
70
  return df[feature_names]
71
 
72
+ def generate_shap_bot_attack_paragraph(index, shap_values, X, encoders=None, class_index=1, top_n=10):
73
+ shap_vals = shap_values.values[index, :, class_index]
74
+ base_val = shap_values.base_values[index, class_index]
75
+ x_vals = X.iloc[index]
76
+ feature_names = X.columns
77
 
78
+ # Decode encoded values
79
+ decoded_vals = {}
80
+ for col in feature_names:
81
+ val = x_vals[col]
82
  try:
83
+ if encoders and col in encoders:
84
+ decoded_vals[col] = encoders[col].inverse_transform([int(val)])[0]
85
+ else:
86
+ decoded_vals[col] = val
87
  except:
88
+ decoded_vals[col] = val
89
 
90
+ # Sort features by absolute SHAP impact
91
+ feature_contribs = list(zip(feature_names, decoded_vals.values(), shap_vals))
92
+ feature_contribs = sorted(feature_contribs, key=lambda x: abs(x[2]), reverse=True)[:top_n]
93
 
94
+ # Separate positive and negative impacts
95
+ positive_impacts = []
96
+ negative_impacts = []
 
97
 
98
+ for fname, fval, sval in feature_contribs:
99
+ line = f" - {fname:20} = {str(fval):<20} contributed {abs(sval):.4f}"
100
+ if sval > 0:
101
+ positive_impacts.append(line)
102
+ elif sval < 0:
103
+ negative_impacts.append(line)
104
 
105
+ final_pred_val = base_val + np.sum(shap_vals)
 
 
 
 
 
 
 
106
 
107
+ explanation = f"\n==== SHAP Explanation for Bot Attack Classification ====\n"
108
+ explanation += f"Base model prediction (Bot): {base_val:.2f}\n"
109
+ explanation += f"Final model output : {final_pred_val:.2f}\n\n"
110
 
111
+ if positive_impacts:
112
+ explanation += "🔺 Factors that INCREASED Bot Likelihood:\n" + "\n".join(positive_impacts) + "\n\n"
 
 
113
 
114
+ if negative_impacts:
115
+ explanation += "🔻 Factors that DECREASED Bot Likelihood:\n" + "\n".join(negative_impacts) + "\n\n"
116
 
117
+ explanation += "📝 These features collectively explain the model's decision.\n"
118
+ return explanation
119
 
120
+ @app.post('/v1/predict')
121
+ def predict():
122
+ try:
123
+ row = request.get_json()
124
+ X = prepare_features(row)
125
+ pred_prob = model.predict_proba(X)[0][1]
126
+ pred_label = int(pred_prob >= 0.5)
127
+
128
+ # Generate SHAP explanation
129
+ shap_values = explainer.shap_values(X)
130
+ explanation = generate_shap_bot_attack_paragraph(0, shap_values, X, encoders)
131
+
132
+ return jsonify({
133
+ "Prediction": "Bot Attack" if pred_label else "Legitimate",
134
+ "Bot Probability": round(float(pred_prob), 4),
135
+ "SHAP Explanation": explanation
136
+ })
137
+
138
+ except Exception as e:
139
+ return jsonify({"error": str(e)}), 500