sheltonmaharesh commited on
Commit
48a11d0
Β·
verified Β·
1 Parent(s): abfab63

Deploy backend Flask app

Browse files
Files changed (1) hide show
  1. bot_detector_api.py +34 -34
bot_detector_api.py CHANGED
@@ -6,13 +6,14 @@ 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 model and preprocessing tools
16
  model = joblib.load("model.joblib")
17
  encoders = joblib.load("encoders.joblib")
18
  scaler = joblib.load("scaler.joblib")
@@ -20,7 +21,7 @@ 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
 
23
- # SHAP Explainer
24
  explainer = shap.TreeExplainer(model)
25
 
26
  def parse_url_params(url):
@@ -69,54 +70,55 @@ def prepare_features(row_dict):
69
  return df[feature_names]
70
 
71
  def generate_shap_explanation(index, shap_values, X, encoders=None, top_n=10):
72
- # Get SHAP values for the instance
73
  if isinstance(shap_values, list):
74
- shap_vals = shap_values[1][index] # Class 1 (Bot Attack)
75
  base_val = explainer.expected_value[1]
76
  else:
77
  shap_vals = shap_values[index]
78
- base_val = explainer.expected_value if np.isscalar(explainer.expected_value) else explainer.expected_value[0]
79
 
80
  # Flatten SHAP values
81
- flat_vals = [float(v[0]) if isinstance(v, (np.ndarray, list)) else float(v) for v in shap_vals]
 
82
 
83
  x_vals = X.iloc[index]
 
 
84
  decoded_vals = {}
85
- for col in X.columns:
 
86
  try:
87
  if encoders and col in encoders:
88
- decoded_vals[col] = encoders[col].inverse_transform([int(x_vals[col])])[0]
89
  else:
90
- decoded_vals[col] = x_vals[col]
91
  except:
92
- decoded_vals[col] = x_vals[col]
93
 
94
- # Top contributing features
95
  feature_contribs = sorted(
96
- zip(X.columns, decoded_vals.values(), flat_vals),
97
- key=lambda x: abs(x[2]),
98
- reverse=True
99
  )[:top_n]
100
 
101
- positive = [f" πŸ”Ί {k:15} = {v} contributed +{abs(s):.4f}" for k, v, s in feature_contribs if s > 0]
102
- negative = [f" πŸ”» {k:15} = {v} contributed -{abs(s):.4f}" for k, v, s in feature_contribs if s < 0]
103
-
104
- final_log_odds = base_val + sum(flat_vals)
105
 
106
- paragraph = f"""
107
  ==== SHAP Explanation for Bot Attack Classification ====
108
 
109
  Base value (log-odds for class 1) : {base_val:.4f}
110
  Predicted log-odds (class 1) : {final_log_odds:.4f}
111
 
112
- """
113
- if positive:
114
- paragraph += "Top factors INCREASING bot likelihood:\n" + "\n".join(positive) + "\n\n"
115
- if negative:
116
- paragraph += "Top factors DECREASING bot likelihood:\n" + "\n".join(negative) + "\n\n"
117
 
118
- paragraph += "πŸ“ These factors combined explain the model's prediction for this session."
119
- return paragraph, base_val, final_log_odds
 
120
 
121
  @app.post('/v1/predict')
122
  def predict():
@@ -124,18 +126,16 @@ def predict():
124
  row = request.get_json()
125
  X = prepare_features(row)
126
 
127
- # Predict probabilities and class
128
- probs = model.predict_proba(X)[0]
129
- pred_class = int(probs[1] >= 0.5)
130
 
131
- # SHAP explanation
132
- shap_vals = explainer.shap_values(X)
133
- explanation, base_val, final_log_odds = generate_shap_explanation(0, shap_vals, X, encoders)
134
 
135
  return jsonify({
136
  "Prediction": "Bot Attack" if pred_class else "Legitimate",
137
- "SHAP Base Value": round(float(base_val), 4),
138
- "SHAP Predicted Value": round(float(final_log_odds), 4),
139
  "SHAP Explanation": explanation
140
  })
141
 
 
6
  from urllib.parse import urlparse, parse_qs
7
  import traceback
8
 
9
+ # Initialize Flask app
10
  app = Flask("Bot detector")
11
 
12
  @app.get('/')
13
  def home():
14
  return "βœ… Welcome to the Bot Prediction API!"
15
 
16
+ # Load model and artifacts
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
25
  explainer = shap.TreeExplainer(model)
26
 
27
  def parse_url_params(url):
 
70
  return df[feature_names]
71
 
72
  def generate_shap_explanation(index, shap_values, X, encoders=None, top_n=10):
 
73
  if isinstance(shap_values, list):
74
+ shap_vals = shap_values[1][index]
75
  base_val = explainer.expected_value[1]
76
  else:
77
  shap_vals = shap_values[index]
78
+ base_val = explainer.expected_value
79
 
80
  # Flatten SHAP values
81
+ shap_scalar_vals = np.array([float(s[0]) if isinstance(s, np.ndarray) else float(s) for s in shap_vals])
82
+ final_log_odds = base_val + np.sum(shap_scalar_vals)
83
 
84
  x_vals = X.iloc[index]
85
+ feature_names = X.columns
86
+
87
  decoded_vals = {}
88
+ for col in feature_names:
89
+ val = x_vals[col]
90
  try:
91
  if encoders and col in encoders:
92
+ decoded_vals[col] = encoders[col].inverse_transform([int(val)])[0]
93
  else:
94
+ decoded_vals[col] = val
95
  except:
96
+ decoded_vals[col] = val
97
 
98
+ # Pair features with SHAP values and sort
99
  feature_contribs = sorted(
100
+ zip(feature_names, decoded_vals.values(), shap_scalar_vals),
101
+ key=lambda x: abs(x[2]), reverse=True
 
102
  )[:top_n]
103
 
104
+ pos_lines = [f" - {f:20} = {str(v):<15} β†’ +{abs(s):.4f}" for f, v, s in feature_contribs if s > 0]
105
+ neg_lines = [f" - {f:20} = {str(v):<15} β†’ -{abs(s):.4f}" for f, v, s in feature_contribs if s < 0]
 
 
106
 
107
+ text = f"""
108
  ==== SHAP Explanation for Bot Attack Classification ====
109
 
110
  Base value (log-odds for class 1) : {base_val:.4f}
111
  Predicted log-odds (class 1) : {final_log_odds:.4f}
112
 
113
+ πŸ”Ί Features INCREASING likelihood of Bot Attack:
114
+ {chr(10).join(pos_lines) or ' - None'}
115
+
116
+ πŸ”» Features DECREASING likelihood of Bot Attack:
117
+ {chr(10).join(neg_lines) or ' - None'}
118
 
119
+ πŸ“ These features collectively explain the model's decision.
120
+ """
121
+ return text, base_val, final_log_odds
122
 
123
  @app.post('/v1/predict')
124
  def predict():
 
126
  row = request.get_json()
127
  X = prepare_features(row)
128
 
129
+ shap_values = explainer.shap_values(X)
130
+ explanation, base_val, final_log_odds = generate_shap_explanation(0, shap_values, X, encoders)
 
131
 
132
+ # Decide based on log-odds
133
+ pred_class = int(final_log_odds >= base_val)
 
134
 
135
  return jsonify({
136
  "Prediction": "Bot Attack" if pred_class else "Legitimate",
137
+ "SHAP Base Value": round(base_val, 4),
138
+ "SHAP Predicted Value": round(final_log_odds, 4),
139
  "SHAP Explanation": explanation
140
  })
141