ankoor123's picture
Upload 3 files
9e948f2 verified
Raw
History Blame Contribute Delete
5.06 kB
"""
NetraLink Entity Resolution — deployment app
Loads:
1. The Siamese-BERT sentence-transformers encoder (this repo)
2. xgboost_matcher.joblib + feature_config.json (this repo)
Reproduces the exact feature pipeline from the training notebook
(pair_features / FEATURE_NAMES) so predictions match training.
"""
import json
import re
import joblib
import numpy as np
from huggingface_hub import hf_hub_download
from rapidfuzz import fuzz
from sentence_transformers import SentenceTransformer, util as st_util
import gradio as gr
REPO_ID = "ankoor123/Netralink-entity-resolution"
# ---------------------------------------------------------------------------
# Load artifacts once at startup
# ---------------------------------------------------------------------------
print("Loading Siamese-BERT encoder...")
siamese_model = SentenceTransformer(REPO_ID)
print("Loading XGBoost matcher + feature config...")
xgb_path = hf_hub_download(REPO_ID, "xgboost_matcher.joblib")
xgb_model = joblib.load(xgb_path)
config_path = hf_hub_download(REPO_ID, "feature_config.json")
with open(config_path, "r", encoding="utf-8") as f:
feature_config = json.load(f)
FEATURE_NAMES = feature_config["feature_names"]
# ---------------------------------------------------------------------------
# Exact feature pipeline from the training notebook (cell 8)
# ---------------------------------------------------------------------------
def normalize_text(text):
text = str(text).lower()
text = re.sub(r"\s+", " ", text)
return text.strip()
def get_norm(m):
if m.get("norm"):
return str(m["norm"])
return normalize_text(m["text"])
def mention_input_text(m):
context = m.get("context", "") or ""
if len(context) > 200:
context = context[:200]
return f"[{m.get('type', '')}] {m['text']} :: {context}"
def pair_features(m1, m2):
emb1 = siamese_model.encode(mention_input_text(m1), convert_to_tensor=True)
emb2 = siamese_model.encode(mention_input_text(m2), convert_to_tensor=True)
cos_sim = float(st_util.cos_sim(emb1, emb2)[0][0])
norm1, norm2 = get_norm(m1), get_norm(m2)
ratio = fuzz.ratio(norm1, norm2) / 100.0
token_sort_ratio = fuzz.token_sort_ratio(norm1, norm2) / 100.0
soundex1 = m1.get("soundex", "")
soundex2 = m2.get("soundex", "")
soundex_match = 1.0 if soundex1 and soundex1 == soundex2 else 0.0
len_diff = abs(len(norm1) - len(norm2))
values = {
"cosine_sim": cos_sim,
"char_ratio": ratio,
"token_sort_ratio": token_sort_ratio,
"soundex_match": soundex_match,
"len_diff": len_diff,
}
# Order strictly by FEATURE_NAMES from feature_config.json
return [values[name] for name in FEATURE_NAMES], values
# ---------------------------------------------------------------------------
# Prediction function
# ---------------------------------------------------------------------------
def predict(text_a, type_a, context_a, text_b, type_b, context_b):
if not text_a.strip() or not text_b.strip():
return "Enter text for both mentions.", {}
m1 = {"text": text_a.strip(), "type": type_a.strip(), "context": context_a.strip() or text_a.strip()}
m2 = {"text": text_b.strip(), "type": type_b.strip(), "context": context_b.strip() or text_b.strip()}
feat_vector, feat_dict = pair_features(m1, m2)
prob = float(xgb_model.predict_proba(np.array([feat_vector], dtype=np.float32))[0, 1])
label = "MATCH" if prob >= 0.5 else "NO MATCH"
result = f"{label} (match probability: {prob:.3f})"
return result, feat_dict
# ---------------------------------------------------------------------------
# Gradio UI (also auto-exposes a REST API at /predict via the "api" tab)
# ---------------------------------------------------------------------------
with gr.Blocks(title="NetraLink Entity Resolution") as demo:
gr.Markdown(
"# NetraLink Entity Resolution\n"
"Siamese-BERT (IndicBERTv2) similarity + XGBoost matcher.\n"
"Enter two entity mentions to check whether they refer to the same entity."
)
with gr.Row():
with gr.Column():
gr.Markdown("**Mention A**")
text_a = gr.Textbox(label="Text")
type_a = gr.Textbox(label="Entity type (e.g. P, ACC, PH)", value="")
context_a = gr.Textbox(label="Context (optional, defaults to Text)", lines=2)
with gr.Column():
gr.Markdown("**Mention B**")
text_b = gr.Textbox(label="Text")
type_b = gr.Textbox(label="Entity type (e.g. P, ACC, PH)", value="")
context_b = gr.Textbox(label="Context (optional, defaults to Text)", lines=2)
btn = gr.Button("Resolve", variant="primary")
output_label = gr.Textbox(label="Result")
output_features = gr.JSON(label="Feature breakdown")
btn.click(
predict,
inputs=[text_a, type_a, context_a, text_b, type_b, context_b],
outputs=[output_label, output_features],
)
if __name__ == "__main__":
demo.launch()