Update handler.py
Browse files- handler.py +67 -41
handler.py
CHANGED
|
@@ -335,73 +335,99 @@ class EndpointHandler:
|
|
| 335 |
weights: Tuple[float, float, float, float] = (1.0, 0.6, 0.8, 0.5),
|
| 336 |
rerank_topn: int = 40,
|
| 337 |
debug_topn: int = 10,
|
|
|
|
| 338 |
) -> Dict[str, Any]:
|
| 339 |
w_sup, w_seg, w_cat, w_rerank = weights
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 346 |
# candidate texts for rerank (prefer cleaned)
|
| 347 |
text_map = {}
|
| 348 |
-
|
| 349 |
for x in seg:
|
| 350 |
-
code = x["s4_code"]
|
| 351 |
-
seg_map[code] = max(seg_map[code], float(x.get("score_01", 0.0)))
|
| 352 |
txt = x.get("label_text_clean") or x.get("label_text") or code
|
| 353 |
text_map[code] = txt
|
| 354 |
-
|
| 355 |
for x in cat:
|
| 356 |
-
code = x["s4_code"]
|
| 357 |
-
cat_map[code] = max(cat_map[code], float(x.get("score_01", 0.0)))
|
| 358 |
txt = x.get("label_text_clean") or x.get("label_text") or code
|
| 359 |
-
# keep existing if seg provided (often better), else catalog
|
| 360 |
text_map.setdefault(code, txt)
|
| 361 |
-
|
| 362 |
-
candidates = set(
|
| 363 |
if not candidates:
|
| 364 |
return {"prediction": None, "method": "no_candidates", "fused_topk": []}
|
| 365 |
-
|
| 366 |
fused = []
|
| 367 |
for code in candidates:
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
fused.append(
|
| 374 |
-
{
|
| 375 |
-
"s4_code": code,
|
| 376 |
-
"score": float(base),
|
| 377 |
-
"components": {"sup": float(s_sup), "seg": float(s_seg), "cat": float(s_cat)},
|
| 378 |
-
"text_for_rerank": text_map.get(code, code),
|
| 379 |
-
}
|
| 380 |
)
|
| 381 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 382 |
fused.sort(key=lambda x: x["score"], reverse=True)
|
| 383 |
-
|
| 384 |
-
#
|
| 385 |
rerank_query = self._make_semantic_query(raw_query) or raw_query.replace("\n", " ").replace("\r", " ")
|
| 386 |
rerank_query = re.sub(r"\s+", " ", rerank_query).strip()
|
| 387 |
-
|
| 388 |
if use_rerank and self.reranker is not None and fused:
|
| 389 |
topn = fused[: min(rerank_topn, len(fused))]
|
| 390 |
pairs = [[rerank_query, c["text_for_rerank"]] for c in topn]
|
|
|
|
| 391 |
rr_raw = self.reranker.predict(pairs).tolist()
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 399 |
fused[: len(topn)] = topn
|
| 400 |
fused.sort(key=lambda x: x["score"], reverse=True)
|
| 401 |
-
|
| 402 |
return {
|
| 403 |
"prediction": fused[0],
|
| 404 |
-
"method": "
|
| 405 |
"fused_topk": fused[:debug_topn],
|
| 406 |
}
|
| 407 |
|
|
|
|
| 335 |
weights: Tuple[float, float, float, float] = (1.0, 0.6, 0.8, 0.5),
|
| 336 |
rerank_topn: int = 40,
|
| 337 |
debug_topn: int = 10,
|
| 338 |
+
rrf_k: int = 60, # bigger = ranks matter a bit less
|
| 339 |
) -> Dict[str, Any]:
|
| 340 |
w_sup, w_seg, w_cat, w_rerank = weights
|
| 341 |
+
|
| 342 |
+
def rrf(rank: Optional[int]) -> float:
|
| 343 |
+
# rank is 1-based. Missing => 0 contribution
|
| 344 |
+
if rank is None:
|
| 345 |
+
return 0.0
|
| 346 |
+
return 1.0 / (rrf_k + rank)
|
| 347 |
+
|
| 348 |
+
def build_rank_map(items: List[Dict[str, Any]], code_key="s4_code") -> Dict[str, int]:
|
| 349 |
+
# assumes items already ordered best->worst
|
| 350 |
+
out = {}
|
| 351 |
+
r = 1
|
| 352 |
+
for x in items:
|
| 353 |
+
code = str(x.get(code_key))
|
| 354 |
+
if code and code not in out:
|
| 355 |
+
out[code] = r
|
| 356 |
+
r += 1
|
| 357 |
+
return out
|
| 358 |
+
|
| 359 |
+
# --- build rank maps (order matters) ---
|
| 360 |
+
sup_rank = build_rank_map(sup)
|
| 361 |
+
|
| 362 |
+
# seg/cat are already in FAISS best->worst order
|
| 363 |
+
seg_rank = build_rank_map(seg)
|
| 364 |
+
cat_rank = build_rank_map(cat)
|
| 365 |
+
|
| 366 |
# candidate texts for rerank (prefer cleaned)
|
| 367 |
text_map = {}
|
|
|
|
| 368 |
for x in seg:
|
| 369 |
+
code = str(x["s4_code"])
|
|
|
|
| 370 |
txt = x.get("label_text_clean") or x.get("label_text") or code
|
| 371 |
text_map[code] = txt
|
|
|
|
| 372 |
for x in cat:
|
| 373 |
+
code = str(x["s4_code"])
|
|
|
|
| 374 |
txt = x.get("label_text_clean") or x.get("label_text") or code
|
|
|
|
| 375 |
text_map.setdefault(code, txt)
|
| 376 |
+
|
| 377 |
+
candidates = set(sup_rank.keys()) | set(seg_rank.keys()) | set(cat_rank.keys())
|
| 378 |
if not candidates:
|
| 379 |
return {"prediction": None, "method": "no_candidates", "fused_topk": []}
|
| 380 |
+
|
| 381 |
fused = []
|
| 382 |
for code in candidates:
|
| 383 |
+
# rank-based fusion (scale-free)
|
| 384 |
+
score = (
|
| 385 |
+
w_sup * rrf(sup_rank.get(code))
|
| 386 |
+
+ w_seg * rrf(seg_rank.get(code))
|
| 387 |
+
+ w_cat * rrf(cat_rank.get(code))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 388 |
)
|
| 389 |
+
|
| 390 |
+
fused.append({
|
| 391 |
+
"s4_code": code,
|
| 392 |
+
"score": float(score),
|
| 393 |
+
"components": {
|
| 394 |
+
# keep some debug info (ranks + raw supervised prob if exists)
|
| 395 |
+
"sup_rank": sup_rank.get(code),
|
| 396 |
+
"seg_rank": seg_rank.get(code),
|
| 397 |
+
"cat_rank": cat_rank.get(code),
|
| 398 |
+
},
|
| 399 |
+
"text_for_rerank": text_map.get(code, code),
|
| 400 |
+
})
|
| 401 |
+
|
| 402 |
fused.sort(key=lambda x: x["score"], reverse=True)
|
| 403 |
+
|
| 404 |
+
# --- optional rerank: use rank, NOT minmax ---
|
| 405 |
rerank_query = self._make_semantic_query(raw_query) or raw_query.replace("\n", " ").replace("\r", " ")
|
| 406 |
rerank_query = re.sub(r"\s+", " ", rerank_query).strip()
|
| 407 |
+
|
| 408 |
if use_rerank and self.reranker is not None and fused:
|
| 409 |
topn = fused[: min(rerank_topn, len(fused))]
|
| 410 |
pairs = [[rerank_query, c["text_for_rerank"]] for c in topn]
|
| 411 |
+
|
| 412 |
rr_raw = self.reranker.predict(pairs).tolist()
|
| 413 |
+
|
| 414 |
+
# rank reranker outputs (best = rank 1)
|
| 415 |
+
order = np.argsort(-np.array(rr_raw))
|
| 416 |
+
rr_rank_map = {}
|
| 417 |
+
for r, idx in enumerate(order, start=1):
|
| 418 |
+
rr_rank_map[idx] = r
|
| 419 |
+
|
| 420 |
+
for i, c in enumerate(topn):
|
| 421 |
+
c["rerank_raw"] = float(rr_raw[i])
|
| 422 |
+
c["rerank_rank"] = rr_rank_map[i]
|
| 423 |
+
c["score"] = float(c["score"] + w_rerank * rrf(c["rerank_rank"]))
|
| 424 |
+
|
| 425 |
fused[: len(topn)] = topn
|
| 426 |
fused.sort(key=lambda x: x["score"], reverse=True)
|
| 427 |
+
|
| 428 |
return {
|
| 429 |
"prediction": fused[0],
|
| 430 |
+
"method": "fused_rrf_rank",
|
| 431 |
"fused_topk": fused[:debug_topn],
|
| 432 |
}
|
| 433 |
|