pridwimnjha's picture
Update inference.py
f306706 verified
Raw
History Blame Contribute Delete
3.41 kB
"""Vertical + Sub-vertical classifier — inference module.
Loads once, exposes classify(category_name) -> dict.
Vertical: closed set of 21 (never 'others'); low confidence -> review flag.
Sub-vertical: masked to the predicted vertical's children; low confidence -> propose-new flag.
"""
import re, json, joblib, numpy as np
from scipy.sparse import hstack
VERT_THR = 0.65 # below -> route vertical to human review
SUB_THR = 0.50 # below -> propose a NEW sub-vertical
_V = joblib.load("vertical_model.joblib") # word_vec, char_vec, clf, classes
_S = joblib.load("subvertical_model.joblib") # clf (LinearSVC), classes
_P = json.load(open("parent_map.json")) # sub_vertical -> vertical
_WV, _CV, _VCLF = _V["word_vec"], _V["char_vec"], _V["clf"]
_VCLASSES = np.array(_VCLF.classes_)
_SCLASSES = np.array(_S["classes"])
_SPARENT = np.array([_P.get(c, "") for c in _SCLASSES])
def _clean(s): return " ".join(str(s).lower().split())
def _vec(s): return hstack([_WV.transform([s]), _CV.transform([s])]).tocsr()
def _suggest_name(name: str) -> str:
"""Derive a candidate sub-vertical name from the category text (offline heuristic)."""
s = str(name)
s = re.sub(r"\([^)]*\)", "", s) # drop "(Malayalam Movie)" etc.
s = re.sub(r"\s+[Ii]n\s+[A-Z][a-zA-Z]+(\s+[A-Z][a-zA-Z]+)*\s*$", "", s) # trailing "In <City>"
if "-" in s: # "Product-Brand" -> keep product
s = s.split("-")[0]
s = " ".join(s.lower().split()).strip(" -/&,.")
return s or _clean(name)
def classify(name: str, vert_thr=VERT_THR, sub_thr=SUB_THR) -> dict:
x = _vec(_clean(name))
# ---- vertical ----
vp = _VCLF.predict_proba(x)[0]
order = vp.argsort()[::-1]
vertical = _VCLASSES[order[0]]; v_conf = float(vp[order[0]])
top3 = [(_VCLASSES[i], round(float(vp[i]), 4)) for i in order[:3]]
# ---- sub-vertical (masked to this vertical's children) ----
mask = _SPARENT == vertical
if mask.any():
m = _S["clf"].decision_function(x)[0].copy()
m[~mask] = -1e9
e = np.exp(m - m.max()); e[~mask] = 0.0; p = e / e.sum()
j = int(p.argmax()); sub = _SCLASSES[j]; s_conf = float(p[j])
else:
sub, s_conf = None, 0.0
propose_new = s_conf < sub_thr
return {
"input": name,
"vertical": vertical,
"vertical_confidence": round(v_conf, 4),
"vertical_review": v_conf < vert_thr,
"vertical_top3": top3,
"sub_vertical": sub,
"sub_vertical_confidence": round(s_conf, 4),
"propose_new_sub_vertical": propose_new,
"suggested_sub_vertical": _suggest_name(name) if propose_new else None,
}
if __name__ == "__main__":
tests = ["Cardiologist Doctors", "Bridal Makeup Artists", "iPhone Repair Service",
"Gold Necklace Dealers-Tanishq", "Yoga Classes For Kids",
"Pakal Pole (Malayalam Movie)", "Drone Camera Dealers-DJI",
"Cattle Feed Suppliers", "Sourdough Bread Bakery", "Wedding Photographer"]
for t in tests:
r = classify(t)
flag = " [REVIEW]" if r["vertical_review"] else ""
newf = " [PROPOSE-NEW-SUB]" if r["propose_new_sub_vertical"] else ""
print(f'{t:38s} -> {r["vertical"]:22s} {r["vertical_confidence"]:.2f}{flag} | '
f'{str(r["sub_vertical"]):32s} {r["sub_vertical_confidence"]:.2f}{newf}')