telen / eval_rerank.py
haidang2405's picture
Update new weights
47bb500 verified
Raw
History Blame Contribute Delete
5.58 kB
"""
TELEN + Cross-encoder re-rank top-5 for MRR boost.
TELEN retrieves top-50, CE re-ranks top-5 to push relevant doc to rank 1.
"""
import sys; sys.path.insert(0, ".")
sys.stdout.reconfigure(encoding='utf-8')
import warnings; warnings.filterwarnings("ignore")
import random, numpy as np, torch, torch.nn as nn, torch.nn.functional as F
from tqdm import tqdm; from collections import defaultdict
from transformers import AutoModel, AutoTokenizer
from src.telern.config import TELENConfig
from src.telern.model import create_model
from src.telern.evaluate import prepare_test_data, build_test_queries, build_test_corpus, compute_metrics
SEED = 42; random.seed(SEED); np.random.seed(SEED); torch.manual_seed(SEED)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
config = TELENConfig()
# Data
test_df = prepare_test_data(config)
queries = build_test_queries(test_df, max_queries=300)
corpus = build_test_corpus(test_df)
corpus_ids = [d["article_id"] for d in corpus]
corpus_law_ids = [d["law_id"] for d in corpus]
train_df = test_df[test_df["year"] <= config.meta.train_split_year]
print(f"Test: {len(queries)} queries, {len(corpus)} docs")
# TELEN
print("Loading TELEN...")
telen = create_model(config).to(device)
ckpt = torch.load(config.output_dir + "/telen_best.pt", map_location=device, weights_only=False)
telen.hypernetwork.load_state_dict(ckpt["hypernetwork"])
telen.state_encoder.load_state_dict(ckpt["state_encoder"])
telen.base_projection.load_state_dict(ckpt["base_projection"])
telen.attn_query.data.copy_(ckpt["attn_query"])
if len(train_df)>0: telen.build_graph(train_df)
print("Encoding corpus...")
c_embs = []
for i in range(0,len(corpus),64):
with torch.no_grad(): r=telen([d["text"] for d in corpus[i:i+64]],use_stochastic=False); c_embs.append(r["embeddings"].cpu())
c_embs = torch.cat(c_embs,dim=0)
# Cross-encoder
print("Loading cross-encoder...")
ce_encoder = AutoModel.from_pretrained("vinai/phobert-base-v2").to(device)
ce_head = nn.Sequential(
nn.Linear(ce_encoder.config.hidden_size, 512), nn.ReLU(), nn.Dropout(0.1),
nn.Linear(512, 256), nn.ReLU(), nn.Dropout(0.1),
nn.Linear(256, 1),
).to(device)
ce_ckpt = torch.load("data/checkpoints/telen/cross_encoder_best.pt", map_location=device, weights_only=False)
ce_encoder.load_state_dict(ce_ckpt["encoder"])
ce_head.load_state_dict(ce_ckpt["head"])
ce_tokenizer = AutoTokenizer.from_pretrained("vinai/phobert-base-v2")
ce_encoder.eval(); ce_head.eval()
def ce_score(query, docs):
scores = []
for i in range(0, len(docs), 32):
b_docs = docs[i:i+32]
enc = ce_tokenizer([query]*len(b_docs), b_docs, padding=True, truncation=True, max_length=256, return_tensors="pt")
with torch.no_grad():
out = ce_encoder(input_ids=enc["input_ids"].to(device), attention_mask=enc["attention_mask"].to(device))
s = torch.sigmoid(ce_head(out.last_hidden_state[:,0,:])).squeeze(-1).cpu().numpy()
if s.ndim==0: s=np.array([s])
scores.append(s)
return np.concatenate(scores)
# Evaluate: TELEN only vs TELEN + CE rerank top-5
rerank_k_vals = [5, 10, 20]
results = {}
# TELEN standalone
print("\n--- TELEN standalone ---")
v3_m = defaultdict(list)
for q in tqdm(queries, desc="TELEN"):
with torch.no_grad(): qe=telen([q["query_text"]],use_stochastic=False)["embeddings"].cpu()
sim=F.cosine_similarity(qe,c_embs).numpy()
si=sim.argsort()[::-1]
rel=np.array([1.0 if corpus_law_ids[j]==q["law_id"] else 0.0 for j in range(len(corpus))])[si]
for j,cid in enumerate(corpus_ids):
if cid==q["query_id"]: p=np.where(si==j)[0]; rel=np.delete(rel,p[0]) if len(p)>0 else None; break
for k in [3,5,10]:
for mn,mv in compute_metrics(rel[:k],[k]).items(): v3_m[mn].append(mv)
results["TELEN"] = {n:np.mean(v) for n,v in v3_m.items()}
# TELEN + CE rerank
for rerank_k in rerank_k_vals:
print(f"\n--- TELEN + CE rerank top-{rerank_k} ---")
all_m = defaultdict(list)
for q in tqdm(queries, desc=f"CE-{rerank_k}"):
with torch.no_grad(): qe=telen([q["query_text"]],use_stochastic=False)["embeddings"].cpu()
sim=F.cosine_similarity(qe,c_embs).numpy()
top_k=sim.argsort()[::-1][:rerank_k]
top_docs=[corpus[idx]["text"] for idx in top_k]
ce_s=ce_score(q["query_text"],top_docs)
reranked=top_k[ce_s.argsort()[::-1]]
remaining=sim.argsort()[::-1][~np.isin(sim.argsort()[::-1],top_k)]
final=np.concatenate([reranked,remaining])
rel=np.array([1.0 if corpus_law_ids[j]==q["law_id"] else 0.0 for j in final])
for j,cid in enumerate(corpus_ids):
if cid==q["query_id"]: p=np.where(final==j)[0]; rel=np.delete(rel,p[0]) if len(p)>0 else None; break
for k in [3,5,10]:
for mn,mv in compute_metrics(rel[:k],[k]).items(): all_m[mn].append(mv)
results[f"TELEN+CE@{rerank_k}"] = {n:np.mean(v) for n,v in all_m.items()}
# Summary
print("\n"+"="*75)
print("MRR BOOST RESULTS")
print("="*75)
h=f"{'Method':<20}"
for m in [3,5,10]: h+=f" {'NDCG@'+str(m):>10} {'MRR@'+str(m):>10}"
print(h); print("-"*len(h))
for name, r in results.items():
row=f"{name:<20}"
for m in [3,5,10]: row+=f" {r[f'ndcg@{m}']:>10.4f} {r[f'mrr@{m}']:>10.4f}"
print(row)
print("\n--- MRR Gain vs TELEN standalone ---")
base=results["TELEN"]
for name in [k for k in results if k!="TELEN"]:
r=results[name]
gains=[(m,(r[f"mrr@{m}"]/max(base[f"mrr@{m}"],1e-6)-1)*100) for m in [3,5,10]]
print(f" {name}: " + " | ".join(f"MRR@{m}: {g:+.1f}%" for m,g in gains))
print("Done!")