sheltonmaharesh commited on
Commit
085c136
·
verified ·
1 Parent(s): 1a5ec58

Deploy backend Flask app

Browse files
Files changed (1) hide show
  1. bot_detector_api.py +49 -43
bot_detector_api.py CHANGED
@@ -6,30 +6,32 @@ from flask import Flask, request, jsonify
6
  from urllib.parse import urlparse, parse_qs
7
  import traceback
8
 
 
9
  app = Flask("Bot detector")
10
 
11
  @app.get('/')
12
  def home():
13
  return "✅ Welcome to the Bot Prediction API!"
14
 
15
- # Load models and artifacts
16
  model = joblib.load("model.joblib")
17
  encoders = joblib.load("encoders.joblib")
18
  scaler = joblib.load("scaler.joblib")
19
  if_model = joblib.load("best_if_model.joblib")
20
  svm_model = joblib.load("best_svm_model.joblib")
21
  feature_names = joblib.load("feature_names.joblib")
 
 
22
  explainer = shap.TreeExplainer(model)
23
 
24
- # Resolve class index
25
- label_map = {v: i for i, v in enumerate(model.classes_)}
26
- BOT_CLASS_INDEX = label_map.get("Bot Attack", 1)
27
 
28
  def parse_url_params(url):
29
  try:
30
- return {k: v[0] if isinstance(v, list) else v
31
- for k, v in parse_qs(urlparse(url).query).items()}
32
- except:
33
  return {}
34
 
35
  def prepare_features(row_dict):
@@ -62,12 +64,16 @@ def prepare_features(row_dict):
62
  iso_score = if_model.decision_function(df_scaled)
63
  svm_score = svm_model.decision_function(df_scaled)
64
 
65
- df['iso_anomaly_prob'] = 1 - float(np.clip((iso_score - iso_score.min()) / (iso_score.max() - iso_score.min() + 1e-9), 0, 1))
66
- df['svm_anomaly_prob'] = 1 - float(np.clip((svm_score - svm_score.min()) / (svm_score.max() - svm_score.min() + 1e-9), 0, 1))
 
 
 
67
 
68
  return df[feature_names]
69
 
70
- def generate_shap_explanation(index, shap_values, X, class_index=BOT_CLASS_INDEX, top_n=10):
 
71
  if isinstance(shap_values, list):
72
  shap_vals = shap_values[class_index][index]
73
  base_val = explainer.expected_value[class_index]
@@ -75,39 +81,50 @@ def generate_shap_explanation(index, shap_values, X, class_index=BOT_CLASS_INDEX
75
  shap_vals = shap_values[index]
76
  base_val = explainer.expected_value if np.isscalar(explainer.expected_value) else explainer.expected_value[0]
77
 
 
78
  shap_scalar_vals = [float(s[0]) if isinstance(s, np.ndarray) else float(s) for s in shap_vals]
79
- final_log_odds = base_val + np.sum(shap_scalar_vals)
 
 
80
 
81
  decoded_vals = {}
82
- for col in X.columns:
83
- val = X.iloc[index][col]
84
  try:
85
- if col in encoders:
86
  decoded_vals[col] = encoders[col].inverse_transform([int(val)])[0]
87
  else:
88
  decoded_vals[col] = val
89
  except:
90
  decoded_vals[col] = val
91
 
92
- feature_contribs = list(zip(X.columns, decoded_vals.values(), shap_scalar_vals))
93
  feature_contribs = sorted(feature_contribs, key=lambda x: abs(x[2]), reverse=True)[:top_n]
94
 
95
- pos = [f" - {f:20} = {v:<20} contributed {abs(s):.4f}" for f, v, s in feature_contribs if s > 0]
96
- neg = [f" - {f:20} = {v:<20} contributed {abs(s):.4f}" for f, v, s in feature_contribs if s < 0]
97
 
98
- explanation = f"""
99
- ==== SHAP Explanation for Bot Attack Classification ====
100
- Base SHAP value (log-odds) : {base_val:.4f}
101
- Model output (log-odds) : {final_log_odds:.4f}
 
 
102
 
103
- 🔺 Factors that INCREASED Bot Likelihood:
104
- {chr(10).join(pos) if pos else ' (None)'}
 
 
 
 
105
 
106
- 🔻 Factors that DECREASED Bot Likelihood:
107
- {chr(10).join(neg) if neg else ' (None)'}
108
 
109
- 📝 These features collectively explain the model's decision.
110
- """
 
 
111
  return explanation
112
 
113
  @app.post('/v1/predict')
@@ -115,26 +132,15 @@ def predict():
115
  try:
116
  row = request.get_json()
117
  X = prepare_features(row)
118
-
119
- # Get prediction probability and predicted class
120
- probs = model.predict_proba(X)[0]
121
- pred_label = int(np.argmax(probs)) # 0 or 1
122
 
123
- # Generate SHAP values
124
  shap_values = explainer.shap_values(X)
125
-
126
- # Generate explanation for class 1
127
- explanation, base_val, final_log_odds = generate_shap_explanation(
128
- index=0,
129
- shap_values=shap_values,
130
- X=X,
131
- class_index=1 # anomaly class
132
- )
133
 
134
  return jsonify({
135
- "Prediction": "Bot Attack" if pred_label == 1 else "Legitimate",
136
- "SHAP Base Value": round(float(base_val), 4),
137
- "SHAP Predicted Value": round(float(final_log_odds), 4),
138
  "SHAP Explanation": explanation
139
  })
140
 
 
6
  from urllib.parse import urlparse, parse_qs
7
  import traceback
8
 
9
+ # Initialize Flask
10
  app = Flask("Bot detector")
11
 
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")
20
  if_model = joblib.load("best_if_model.joblib")
21
  svm_model = joblib.load("best_svm_model.joblib")
22
  feature_names = joblib.load("feature_names.joblib")
23
+
24
+ # SHAP explainer (for tree-based models)
25
  explainer = shap.TreeExplainer(model)
26
 
27
+ def sigmoid(x):
28
+ return 1 / (1 + np.exp(-x))
 
29
 
30
  def parse_url_params(url):
31
  try:
32
+ query = urlparse(url).query
33
+ return {k: v[0] if isinstance(v, list) else v for k, v in parse_qs(query).items()}
34
+ except Exception:
35
  return {}
36
 
37
  def prepare_features(row_dict):
 
64
  iso_score = if_model.decision_function(df_scaled)
65
  svm_score = svm_model.decision_function(df_scaled)
66
 
67
+ iso_prob = 1 - float(np.clip((iso_score - iso_score.min()) / (iso_score.max() - iso_score.min() + 1e-9), 0, 1))
68
+ svm_prob = 1 - float(np.clip((svm_score - svm_score.min()) / (svm_score.max() - svm_score.min() + 1e-9), 0, 1))
69
+
70
+ df['iso_anomaly_prob'] = iso_prob
71
+ df['svm_anomaly_prob'] = svm_prob
72
 
73
  return df[feature_names]
74
 
75
+ def generate_shap_bot_attack_paragraph(index, shap_values, X, encoders=None, class_index=1, top_n=10):
76
+ # Handle binary vs multiclass
77
  if isinstance(shap_values, list):
78
  shap_vals = shap_values[class_index][index]
79
  base_val = explainer.expected_value[class_index]
 
81
  shap_vals = shap_values[index]
82
  base_val = explainer.expected_value if np.isscalar(explainer.expected_value) else explainer.expected_value[0]
83
 
84
+ # Fix: flatten SHAP values
85
  shap_scalar_vals = [float(s[0]) if isinstance(s, np.ndarray) else float(s) for s in shap_vals]
86
+
87
+ x_vals = X.iloc[index]
88
+ feature_names = X.columns
89
 
90
  decoded_vals = {}
91
+ for col in feature_names:
92
+ val = x_vals[col]
93
  try:
94
+ if encoders and col in encoders:
95
  decoded_vals[col] = encoders[col].inverse_transform([int(val)])[0]
96
  else:
97
  decoded_vals[col] = val
98
  except:
99
  decoded_vals[col] = val
100
 
101
+ feature_contribs = list(zip(feature_names, decoded_vals.values(), shap_scalar_vals))
102
  feature_contribs = sorted(feature_contribs, key=lambda x: abs(x[2]), reverse=True)[:top_n]
103
 
104
+ positive_impacts = []
105
+ negative_impacts = []
106
 
107
+ for fname, fval, sval in feature_contribs:
108
+ line = f" - {fname:20} = {str(fval):<20} contributed {abs(sval):.4f}"
109
+ if sval > 0:
110
+ positive_impacts.append(line)
111
+ elif sval < 0:
112
+ negative_impacts.append(line)
113
 
114
+ final_log_odds = base_val + np.sum(shap_scalar_vals)
115
+ final_pred_val = sigmoid(final_log_odds)
116
+
117
+ explanation = f"\n==== SHAP Explanation for Bot Attack Classification ====\n"
118
+ explanation += f"Base model log-odds : {base_val:.4f}\n"
119
+ explanation += f"Final model probability : {final_pred_val:.4f}\n\n"
120
 
121
+ if positive_impacts:
122
+ explanation += "🔺 Factors that INCREASED Bot Likelihood:\n" + "\n".join(positive_impacts) + "\n\n"
123
 
124
+ if negative_impacts:
125
+ explanation += "🔻 Factors that DECREASED Bot Likelihood:\n" + "\n".join(negative_impacts) + "\n\n"
126
+
127
+ explanation += "📝 These features collectively explain the model's decision.\n"
128
  return explanation
129
 
130
  @app.post('/v1/predict')
 
132
  try:
133
  row = request.get_json()
134
  X = prepare_features(row)
135
+ pred_prob = model.predict_proba(X)[0][1]
136
+ pred_label = int(pred_prob >= 0.5)
 
 
137
 
 
138
  shap_values = explainer.shap_values(X)
139
+ explanation = generate_shap_bot_attack_paragraph(0, shap_values, X, encoders)
 
 
 
 
 
 
 
140
 
141
  return jsonify({
142
+ "Prediction": "Bot Attack" if pred_label else "Legitimate",
143
+ "Bot Probability": round(float(pred_prob), 4),
 
144
  "SHAP Explanation": explanation
145
  })
146