COCODEDE04 commited on
Commit
4efe09d
·
verified ·
1 Parent(s): fde4b2c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +103 -30
app.py CHANGED
@@ -2,13 +2,19 @@ import json
2
  import numpy as np
3
  import tensorflow as tf
4
  import gradio as gr
 
 
5
 
6
- # ==== CONFIG ====
7
- MODEL_PATH = "best_model.h5"
8
- STATS_PATH = "means_std.json"
 
 
9
  CLASSES = ["Top", "Mid-Top", "Mid", "Mid-Low", "Low"]
10
- # ================
11
 
 
 
 
12
  print("Loading model and stats...")
13
  model = tf.keras.models.load_model(MODEL_PATH, compile=False)
14
 
@@ -18,57 +24,124 @@ with open(STATS_PATH, "r") as f:
18
  FEATURES = list(stats.keys())
19
  print("Feature order:", FEATURES)
20
 
21
- def zscore(val, mean, sd):
 
 
 
22
  try:
23
- val = float(val)
24
  except Exception:
25
  return 0.0
26
- if sd == 0 or sd is None:
27
  return 0.0
28
- return (val - mean) / sd
29
 
30
- def coral_probs_from_logits(logits_np):
 
 
 
 
31
  logits = tf.convert_to_tensor(logits_np, dtype=tf.float32)
32
- sig = tf.math.sigmoid(logits)
33
- left = tf.concat([tf.ones_like(sig[:, :1]), sig], axis=1)
34
  right = tf.concat([sig, tf.zeros_like(sig[:, :1])], axis=1)
35
  probs = tf.clip_by_value(left - right, 1e-12, 1.0)
36
  return probs.numpy()
37
 
38
- def predict_from_json(input_json):
39
- # Expect a dictionary with feature:value pairs
40
- if not isinstance(input_json, dict):
41
- return {"error": "Expected JSON object mapping feature -> value."}
 
 
 
42
 
43
- # Prepare data
44
- z_list = []
45
  for f in FEATURES:
46
- v = input_json.get(f, 0)
47
- z = zscore(v, stats[f]["mean"], stats[f]["std"])
48
  z_list.append(z)
49
- X = np.array([z_list], dtype=np.float32)
50
 
 
51
  raw = model.predict(X, verbose=0)
 
 
 
 
52
  if raw.shape[1] == len(CLASSES) - 1:
53
- probs = coral_probs_from_logits(raw)[0]
 
 
 
 
 
 
 
 
 
 
 
54
  else:
55
- probs = raw[0]
56
 
57
  pred_idx = int(np.argmax(probs))
58
- output = {
 
 
 
59
  "probabilities": {CLASSES[i]: float(probs[i]) for i in range(len(CLASSES))},
60
  "predicted_state": CLASSES[pred_idx],
61
  }
62
- return output
63
 
64
- # ==== Gradio UI ====
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  demo = gr.Interface(
66
- fn=predict_from_json,
67
- inputs=gr.JSON(label="Feature dictionary (JSON)"),
68
  outputs="json",
69
- title="Static Fingerprint Predictor",
70
- description="POST JSON to /run/predict with your feature values."
 
71
  )
72
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  if __name__ == "__main__":
74
- demo.launch()
 
 
2
  import numpy as np
3
  import tensorflow as tf
4
  import gradio as gr
5
+ from fastapi import FastAPI, HTTPException
6
+ from typing import Dict, Any
7
 
8
+ # =========================
9
+ # Config
10
+ # =========================
11
+ MODEL_PATH = "best_model.h5" # your uploaded model
12
+ STATS_PATH = "means_std.json" # {"feature": {"mean": x, "std": y}, ...}
13
  CLASSES = ["Top", "Mid-Top", "Mid", "Mid-Low", "Low"]
 
14
 
15
+ # =========================
16
+ # Load artifacts
17
+ # =========================
18
  print("Loading model and stats...")
19
  model = tf.keras.models.load_model(MODEL_PATH, compile=False)
20
 
 
24
  FEATURES = list(stats.keys())
25
  print("Feature order:", FEATURES)
26
 
27
+ # =========================
28
+ # Helpers
29
+ # =========================
30
+ def _zscore(val: Any, mean: float, sd: float) -> float:
31
  try:
32
+ v = float(val)
33
  except Exception:
34
  return 0.0
35
+ if sd is None or sd == 0:
36
  return 0.0
37
+ return (v - mean) / sd
38
 
39
+ def _coral_probs_from_logits(logits_np: np.ndarray) -> np.ndarray:
40
+ """
41
+ logits_np: (N, K-1) linear outputs.
42
+ Returns probabilities (N, K) with p_k = σ(z_{k-1}) - σ(z_k), and boundaries 1/0.
43
+ """
44
  logits = tf.convert_to_tensor(logits_np, dtype=tf.float32)
45
+ sig = tf.math.sigmoid(logits) # (N, K-1)
46
+ left = tf.concat([tf.ones_like(sig[:, :1]), sig], axis=1)
47
  right = tf.concat([sig, tf.zeros_like(sig[:, :1])], axis=1)
48
  probs = tf.clip_by_value(left - right, 1e-12, 1.0)
49
  return probs.numpy()
50
 
51
+ def _predict_core(ratios: Dict[str, Any]) -> Dict[str, Any]:
52
+ """
53
+ ratios: dict mapping feature -> raw numeric value.
54
+ Returns: dict with predicted_state, probabilities, z_scores, missing, input_ok.
55
+ """
56
+ # Validate presence (we still accept missing and fill 0.0 after z-score)
57
+ missing = [f for f in FEATURES if f not in ratios]
58
 
59
+ # Build z-score vector in exact FEATURE order
60
+ z_list, z_scores = [], {}
61
  for f in FEATURES:
62
+ z = _zscore(ratios.get(f, 0.0), stats[f]["mean"], stats[f]["std"])
 
63
  z_list.append(z)
64
+ z_scores[f] = z
65
 
66
+ X = np.array([z_list], dtype=np.float32) # (1, D)
67
  raw = model.predict(X, verbose=0)
68
+
69
+ # Softmax (K) vs CORAL (K-1)
70
+ if raw.ndim != 2:
71
+ raise ValueError(f"Unexpected model output shape: {raw.shape}")
72
  if raw.shape[1] == len(CLASSES) - 1:
73
+ probs = _coral_probs_from_logits(raw)[0] # (K,)
74
+ elif raw.shape[1] == len(CLASSES):
75
+ probs = raw[0] # (K,)
76
+ else:
77
+ raise ValueError(f"Model output width {raw.shape[1]} incompatible with classes {len(CLASSES)}")
78
+
79
+ # Safety: normalize if not a perfect prob. vector
80
+ probs = np.maximum(probs, 0.0)
81
+ s = probs.sum()
82
+ if s <= 0:
83
+ # fallback uniform if something pathological happens
84
+ probs = np.ones(len(CLASSES), dtype=np.float32) / float(len(CLASSES))
85
  else:
86
+ probs = probs / s
87
 
88
  pred_idx = int(np.argmax(probs))
89
+ return {
90
+ "input_ok": len(missing) == 0,
91
+ "missing": missing,
92
+ "z_scores": z_scores,
93
  "probabilities": {CLASSES[i]: float(probs[i]) for i in range(len(CLASSES))},
94
  "predicted_state": CLASSES[pred_idx],
95
  }
 
96
 
97
+ # =========================
98
+ # Gradio adapter (UI)
99
+ # =========================
100
+ def _gradio_adapter(payload):
101
+ """
102
+ Accepts either:
103
+ - a dict {feature: value, ...}
104
+ - a list with one dict [ {feature: value, ...} ]
105
+ """
106
+ if isinstance(payload, list) and len(payload) == 1 and isinstance(payload[0], dict):
107
+ payload = payload[0]
108
+ if not isinstance(payload, dict):
109
+ return {"error": "Expected JSON object mapping feature -> value."}
110
+ return _predict_core(payload)
111
+
112
  demo = gr.Interface(
113
+ fn=_gradio_adapter,
114
+ inputs=gr.JSON(label="ratios JSON (dict of feature -> value)"),
115
  outputs="json",
116
+ title="Static Fingerprint Model API",
117
+ description="Programmatic use: POST a raw dict to /predict. UI here is for quick manual checks.",
118
+ allow_flagging="never"
119
  )
120
 
121
+ # =========================
122
+ # FastAPI app (sync endpoint)
123
+ # =========================
124
+ api = FastAPI()
125
+
126
+ @api.get("/health")
127
+ def health():
128
+ return {"status": "ok", "features": FEATURES, "classes": CLASSES}
129
+
130
+ @api.post("/predict")
131
+ def predict_endpoint(payload: Any):
132
+ # Allow list-of-one and dict
133
+ if isinstance(payload, list) and len(payload) == 1 and isinstance(payload[0], dict):
134
+ payload = payload[0]
135
+ if not isinstance(payload, dict):
136
+ raise HTTPException(status_code=400, detail="Expected JSON object mapping feature -> value.")
137
+ try:
138
+ return _predict_core(payload)
139
+ except Exception as e:
140
+ raise HTTPException(status_code=500, detail=str(e))
141
+
142
+ # Mount Gradio UI at "/" and expose FastAPI routes alongside it
143
+ app = gr.mount_gradio_app(api, demo, path="/")
144
+
145
  if __name__ == "__main__":
146
+ # local dev run (HF Spaces will ignore this and use its own server)
147
+ demo.launch()