COCODEDE04 commited on
Commit
fdeffed
·
verified ·
1 Parent(s): 95af2b2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +70 -120
app.py CHANGED
@@ -1,166 +1,116 @@
1
- import os
2
  import json
3
  import numpy as np
4
- import gradio as gr
5
  import tensorflow as tf
 
 
 
6
 
7
  # ---------- CONFIG ----------
8
- MODEL_PATH = "best_model.h5" # <- you converted & uploaded this
9
- STATS_PATH = "Means & Std for Excel.json" # <- keep exact filename
10
- CLASSES = ["Top", "Mid-Top", "Mid", "Mid-Low", "Low"] # your group order
11
  # ----------------------------
12
 
13
  print("Loading model and stats...")
14
  model = tf.keras.models.load_model(MODEL_PATH, compile=False)
15
-
16
  with open(STATS_PATH, "r") as f:
17
  stats = json.load(f)
18
 
19
- # Feature order: use the order from JSON to remain consistent
20
  FEATURES = list(stats.keys())
21
  print("Feature order:", FEATURES)
22
 
23
- def _zscore(val: float, mean: float, sd: float) -> float:
24
- # Robust z-score (avoid division by zero)
25
  try:
26
  v = float(val)
27
- except (TypeError, ValueError):
28
  v = 0.0
29
- return 0.0 if (sd is None or sd == 0) else (v - mean) / sd
30
 
31
- def predict_core(ratios: dict):
32
- """
33
- ratios: dict mapping feature name -> raw numeric ratio.
34
- Returns a dict with predicted_state, probabilities, z_scores, missing.
35
- """
36
 
37
- # Validate that all required features are present
38
- missing = [f for f in FEATURES if f not in ratios]
 
 
 
 
 
 
39
 
40
- # Build z-score vector (same order as training)
 
41
  zscores = []
42
- zscores_dict = {}
43
  for f in FEATURES:
44
  mean = stats[f]["mean"]
45
- sd = stats[f]["std"]
46
- val = ratios.get(f, 0.0)
47
- z = _zscore(val, mean, sd)
48
  zscores.append(z)
49
- zscores_dict[f] = z
50
 
51
- # Predict logits (shape: (1, K-1))
52
  X = np.array([zscores], dtype=np.float32)
53
- logits = model.predict(X, verbose=0)[0] # (K-1,)
54
-
55
- # --- CORAL decoding with monotonic enforcement ---
56
- # 1. Compute cumulative sigmoids
57
- sig = tf.math.sigmoid(logits).numpy()
58
-
59
- # 2. Enforce monotonic decrease: σ1 >= σ2 >= ... >= σ_{K-1}
60
- sig_mono = np.minimum.accumulate(sig)
61
-
62
- # 3. Construct boundaries [1, σ1, σ2, ..., σ_{K-1}, 0]
63
- edges = np.concatenate(([1.0], sig_mono, [0.0]))
64
-
65
- # 4. Compute adjacent differences -> per-class probabilities
66
- probs = edges[:-1] - edges[1:]
67
-
68
- # 5. Normalise just in case of minor floating-point drift
69
- s = probs.sum()
70
- if s > 0:
71
- probs = probs / s
72
-
73
- # Map to class labels
74
- probs_dict = {CLASSES[i]: float(probs[i]) for i in range(len(CLASSES))}
75
-
76
- # Determine the predicted state (highest probability)
77
- pred_state = max(probs_dict, key=probs_dict.get)
78
-
79
- # Optional: print sanity check for internal testing
80
- # print("Sigmoids:", sig)
81
- # print("Probabilities:", probs_dict)
82
- # print("Sum of probs:", probs.sum())
83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  return {
85
- "input_ok": len(missing) == 0,
86
- "missing": missing,
87
- "z_scores": zscores_dict,
88
- "probabilities": probs_dict,
89
  "predicted_state": pred_state
90
  }
91
- # ---------- Gradio adapter ----------
92
- def predict_from_json(payload, x_api_key: str = ""):
93
- """
94
- Gradio will pass the JSON from the UI or /run/predict.
95
- We accept either:
96
- { ...feature: value... }
97
- or [ { ...feature: value... } ] (unwrap common API client shape)
98
- """
99
 
100
- # Unwrap if list-of-one was sent
 
101
  if isinstance(payload, list) and len(payload) == 1 and isinstance(payload[0], dict):
102
  payload = payload[0]
103
-
104
  if not isinstance(payload, dict):
105
- return {"error": "Invalid payload: expected a JSON object mapping feature -> value."}
106
-
107
  return predict_core(payload)
108
 
109
- # ---------- AUTOLOAD DEMO INPUT ----------
110
- DEMO_PATH = "example_input.json" # <-- name of your uploaded file
111
- demo_data = {}
 
 
 
112
 
113
- if os.path.exists(DEMO_PATH):
114
- try:
115
- with open(DEMO_PATH, "r") as f:
116
- demo_data = json.load(f)
117
- # Unwrap if the file uses {"data":[{...}]} format
118
- if isinstance(demo_data, dict) and "data" in demo_data and isinstance(demo_data["data"], list):
119
- demo_data = demo_data["data"][0]
120
- except Exception as e:
121
- print("⚠️ Could not load demo input:", e)
122
- else:
123
- # Default zeros if file not found
124
- demo_data = {f: 0.0 for f in FEATURES}
125
- # -----------------------------------------
126
 
 
 
 
 
127
 
128
- # ---------- DEFINE GRADIO INTERFACE ----------
129
- # --- UI (unchanged) ---
130
- iface = gr.Interface(
131
  fn=predict_from_json,
132
  inputs=gr.JSON(label="ratios JSON (dict of feature -> value)"),
133
  outputs="json",
134
  title="Static Fingerprint Model API",
135
  description="POST your 21 ratios as a JSON dict. Returns probabilities + predicted state."
136
  )
137
-
138
- # --- FastAPI app with a simple REST endpoint ---
139
- from fastapi import FastAPI, Request
140
- import gradio as gr
141
-
142
- app = FastAPI()
143
- # Mount the Gradio UI at the root so your Space page still works
144
- app = gr.mount_gradio_app(app, iface, path="/")
145
-
146
- @app.post("/predict")
147
- async def api_predict(req: Request):
148
- """
149
- Accepts either:
150
- 1) raw dict: {"autosuf_oper":1.2, ...}
151
- 2) gradio format: {"data":[{...}]}
152
- """
153
- body = await req.json()
154
- if isinstance(body, dict) and "data" in body and isinstance(body["data"], list) and body["data"]:
155
- payload = body["data"][0] # unwrap Gradio shape
156
- elif isinstance(body, dict):
157
- payload = body # raw dict
158
- else:
159
- return {"error": "Invalid payload. Send a JSON object of feature->value OR {'data':[that_object]}"}
160
-
161
- try:
162
- return predict_from_json(payload) # reuse your existing function
163
- except Exception as e:
164
- return {"error": f"{type(e).__name__}: {e}"}
165
-
166
- # Spaces will auto-run with uvicorn; no need to call launch() here.
 
 
1
  import json
2
  import numpy as np
 
3
  import tensorflow as tf
4
+ import gradio as gr
5
+ from fastapi import FastAPI, Request
6
+ from fastapi.middleware.cors import CORSMiddleware
7
 
8
  # ---------- CONFIG ----------
9
+ MODEL_PATH = "best_model.h5" # or best_model.keras if that’s what you uploaded
10
+ STATS_PATH = "Means & Std for Excel.json" # exact filename
11
+ CLASSES = ["Top", "Mid-Top", "Mid", "Mid-Low", "Low"]
12
  # ----------------------------
13
 
14
  print("Loading model and stats...")
15
  model = tf.keras.models.load_model(MODEL_PATH, compile=False)
 
16
  with open(STATS_PATH, "r") as f:
17
  stats = json.load(f)
18
 
 
19
  FEATURES = list(stats.keys())
20
  print("Feature order:", FEATURES)
21
 
22
+ def _z(val): # safe z-score
 
23
  try:
24
  v = float(val)
25
+ except Exception:
26
  v = 0.0
27
+ return v
28
 
29
+ def _zscore(val, mean, sd):
30
+ v = _z(val)
31
+ return 0.0 if (sd is None or sd == 0) else (v - mean) / sd
 
 
32
 
33
+ def coral_probs_from_logits(logits_np):
34
+ import tensorflow as tf
35
+ logits = tf.convert_to_tensor(logits_np, dtype=tf.float32) # (1, K-1)
36
+ sig = tf.math.sigmoid(logits) # (1, K-1)
37
+ left = tf.concat([tf.ones_like(sig[:, :1]), sig], axis=1)
38
+ right = tf.concat([sig, tf.zeros_like(sig[:, :1])], axis=1)
39
+ probs = tf.clip_by_value(left - right, 1e-12, 1.0)
40
+ return probs.numpy()
41
 
42
+ def predict_core(ratios: dict):
43
+ # build z vector in fixed order
44
  zscores = []
45
+ z_map = {}
46
  for f in FEATURES:
47
  mean = stats[f]["mean"]
48
+ sd = stats[f]["std"]
49
+ val = ratios.get(f, 0.0)
50
+ z = _zscore(val, mean, sd)
51
  zscores.append(z)
52
+ z_map[f] = z
53
 
 
54
  X = np.array([zscores], dtype=np.float32)
55
+ y = model.predict(X, verbose=0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
+ # handle either softmax K or CORAL K-1
58
+ if y.ndim == 2 and y.shape[1] == len(CLASSES):
59
+ probs = y[0]
60
+ elif y.ndim == 2 and y.shape[1] == len(CLASSES) - 1:
61
+ probs = coral_probs_from_logits(y)[0]
62
+ else:
63
+ # fallback: normalize positive scores
64
+ s = y[0].astype(np.float64)
65
+ if s.ndim == 0:
66
+ s = np.array([float(s)], dtype=np.float64)
67
+ s = np.maximum(s, 0.0)
68
+ probs = s / s.sum() if s.sum() > 0 else np.ones(len(CLASSES)) / len(CLASSES)
69
+
70
+ pred_idx = int(np.argmax(probs))
71
+ pred_state = CLASSES[pred_idx]
72
  return {
73
+ "input_ok": True,
74
+ "missing": [f for f in FEATURES if f not in ratios],
75
+ "z_scores": z_map,
76
+ "probabilities": {CLASSES[i]: float(probs[i]) for i in range(len(CLASSES))},
77
  "predicted_state": pred_state
78
  }
 
 
 
 
 
 
 
 
79
 
80
+ def predict_from_json(payload):
81
+ # accept raw dict OR list-of-one dict
82
  if isinstance(payload, list) and len(payload) == 1 and isinstance(payload[0], dict):
83
  payload = payload[0]
 
84
  if not isinstance(payload, dict):
85
+ return {"error": "Invalid payload: send a JSON object mapping feature->value."}
 
86
  return predict_core(payload)
87
 
88
+ # ------------------ FastAPI + Gradio ------------------
89
+ app = FastAPI()
90
+ app.add_middleware(
91
+ CORSMiddleware,
92
+ allow_origins=["*"], allow_methods=["*"], allow_headers=["*"],
93
+ )
94
 
95
+ # Plain REST endpoint for Excel/VBA (raw dict)
96
+ @app.post("/predict")
97
+ async def api_predict(req: Request):
98
+ body = await req.json()
99
+ if isinstance(body, dict) and "data" in body and isinstance(body["data"], list) and body["data"]:
100
+ body = body["data"][0] # unwrap gradio shape
101
+ return predict_from_json(body)
 
 
 
 
 
 
102
 
103
+ # Optional health check
104
+ @app.get("/health")
105
+ def health():
106
+ return {"ok": True}
107
 
108
+ # Mount UI at root
109
+ ui = gr.Interface(
 
110
  fn=predict_from_json,
111
  inputs=gr.JSON(label="ratios JSON (dict of feature -> value)"),
112
  outputs="json",
113
  title="Static Fingerprint Model API",
114
  description="POST your 21 ratios as a JSON dict. Returns probabilities + predicted state."
115
  )
116
+ app = gr.mount_gradio_app(app, ui, path="/")