COCODEDE04 commited on
Commit
7dc78bf
·
verified ·
1 Parent(s): 4b96a3d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +38 -33
app.py CHANGED
@@ -356,11 +356,7 @@ async def predict(req: Request):
356
  if not isinstance(payload, dict):
357
  return JSONResponse(status_code=400, content={"error": "Expected JSON object"})
358
 
359
- # default SHAP block – will be overwritten if explanation succeeds
360
- shap_out = {"error": "SHAP not computed"}
361
-
362
  # ---------- PREPROCESSING ----------
363
- # Build in EXACT training order
364
  raw = build_raw_vector(payload) # may contain NaNs
365
  raw_imp = apply_imputer_if_any(raw) # impute
366
  z_vec, z_detail, z_mode = apply_scaling_or_stats(raw_imp) # scale / z-score
@@ -375,34 +371,49 @@ async def predict(req: Request):
375
  missing = [f for i, f in enumerate(FEATURES) if np.isnan(raw[i])]
376
 
377
  # ---------- SHAP EXPLANATION (predicted class only) ----------
 
378
  if EXPLAINER is not None:
379
  try:
380
- # X is already z-space: shape (1, n_features)
381
  shap_vals = EXPLAINER.shap_values(X, nsamples=100)
382
 
383
- # Case 1: multi-output -> list of length K, each (1, n_features)
384
  if isinstance(shap_vals, list):
385
- shap_vec = np.array(shap_vals[pred_idx][0], dtype=float)
386
- exp_val_raw = EXPLAINER.expected_value
387
- if isinstance(exp_val_raw, (list, np.ndarray)):
388
- exp_val = float(exp_val_raw[pred_idx])
389
- else:
390
- exp_val = float(exp_val_raw)
391
-
392
- # Case 2: single-output -> ndarray (1, n_features)
393
- elif isinstance(shap_vals, np.ndarray):
394
- shap_vec = np.array(shap_vals[0], dtype=float)
395
- exp_val_raw = EXPLAINER.expected_value
396
- if isinstance(exp_val_raw, (list, np.ndarray)):
397
- exp_val = float(exp_val_raw[0])
 
398
  else:
399
- exp_val = float(exp_val_raw)
400
-
401
- # Anything else – we consider wrong type
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402
  else:
403
- raise TypeError(f"Unsupported SHAP return type: {type(shap_vals)}")
404
 
405
- # Map feature -> SHAP contribution (for the predicted class)
406
  shap_feature_contribs = {
407
  FEATURES[i]: float(shap_vec[i])
408
  for i in range(len(FEATURES))
@@ -415,10 +426,7 @@ async def predict(req: Request):
415
  }
416
 
417
  except Exception as e:
418
- shap_out = {
419
- "error": str(e),
420
- "trace": traceback.format_exc()
421
- }
422
  else:
423
  shap_out = {"error": "SHAP not available on server"}
424
 
@@ -431,7 +439,7 @@ async def predict(req: Request):
431
  "scaler": bool(scaler),
432
  "z_mode": z_mode,
433
  },
434
- "z_scores": z_detail, # per feature
435
  "probabilities": probs_dict, # per class
436
  "predicted_state": CLASSES[pred_idx],
437
  "shap": shap_out, # SHAP for predicted state only
@@ -445,8 +453,5 @@ async def predict(req: Request):
445
  except Exception as e:
446
  return JSONResponse(
447
  status_code=500,
448
- content={
449
- "error": str(e),
450
- "trace": traceback.format_exc()
451
- }
452
  )
 
356
  if not isinstance(payload, dict):
357
  return JSONResponse(status_code=400, content={"error": "Expected JSON object"})
358
 
 
 
 
359
  # ---------- PREPROCESSING ----------
 
360
  raw = build_raw_vector(payload) # may contain NaNs
361
  raw_imp = apply_imputer_if_any(raw) # impute
362
  z_vec, z_detail, z_mode = apply_scaling_or_stats(raw_imp) # scale / z-score
 
371
  missing = [f for i, f in enumerate(FEATURES) if np.isnan(raw[i])]
372
 
373
  # ---------- SHAP EXPLANATION (predicted class only) ----------
374
+ shap_out = {"error": "SHAP not computed"}
375
  if EXPLAINER is not None:
376
  try:
 
377
  shap_vals = EXPLAINER.shap_values(X, nsamples=100)
378
 
379
+ # 1) Pull out the array for the predicted class (if multi-output)
380
  if isinstance(shap_vals, list):
381
+ raw_sv = np.array(shap_vals[pred_idx])
382
+ else:
383
+ raw_sv = np.array(shap_vals)
384
+
385
+ # 2) Normalize shapes: we want a 1D vector of length n_features
386
+ # Possible shapes we might see:
387
+ # (n_features,)
388
+ # (1, n_features)
389
+ # (n_samples, n_features) -> take first sample
390
+ if raw_sv.ndim == 1:
391
+ shap_vec = raw_sv.astype(float)
392
+ elif raw_sv.ndim == 2:
393
+ if raw_sv.shape[0] == 1:
394
+ shap_vec = raw_sv[0].astype(float)
395
  else:
396
+ # assume shape (n_samples, n_features); take first sample
397
+ shap_vec = raw_sv[0].astype(float)
398
+ else:
399
+ # last resort: flatten sample dims, take first "row"
400
+ raw_sv = raw_sv.reshape(raw_sv.shape[0], -1)
401
+ shap_vec = raw_sv[0].astype(float)
402
+
403
+ if shap_vec.shape[0] != len(FEATURES):
404
+ raise ValueError(
405
+ f"Unexpected SHAP vector length {shap_vec.shape[0]} "
406
+ f"(expected {len(FEATURES)})"
407
+ )
408
+
409
+ # 3) Expected value: baseline logit/prob for that class
410
+ exp_raw = EXPLAINER.expected_value
411
+ if isinstance(exp_raw, (list, np.ndarray)):
412
+ exp_val = float(np.array(exp_raw)[pred_idx])
413
  else:
414
+ exp_val = float(exp_raw)
415
 
416
+ # 4) Map feature -> SHAP contribution
417
  shap_feature_contribs = {
418
  FEATURES[i]: float(shap_vec[i])
419
  for i in range(len(FEATURES))
 
426
  }
427
 
428
  except Exception as e:
429
+ shap_out = {"error": str(e), "trace": traceback.format_exc()}
 
 
 
430
  else:
431
  shap_out = {"error": "SHAP not available on server"}
432
 
 
439
  "scaler": bool(scaler),
440
  "z_mode": z_mode,
441
  },
442
+ "z_scores": z_detail, # per feature (z-space)
443
  "probabilities": probs_dict, # per class
444
  "predicted_state": CLASSES[pred_idx],
445
  "shap": shap_out, # SHAP for predicted state only
 
453
  except Exception as e:
454
  return JSONResponse(
455
  status_code=500,
456
+ content={"error": str(e), "trace": traceback.format_exc()}
 
 
 
457
  )