Infomax-Reranker / benchmark /bm25_pools.py
Angshul's picture
Upload 14 files
d3fd67f verified
Raw History Blame Contribute Delete
2.74 kB
"""First stage: Lucene-style BM25 (k1=0.9, b=0.4, Porter stemming) top-100 pools per test query."""
import json
import os
import re
import time
import numpy as np
import scipy.sparse as sp
from nltk.stem import PorterStemmer
from sklearn.feature_extraction.text import ENGLISH_STOP_WORDS, CountVectorizer
from common import DATASETS, OUT, evaluate, load_corpus, load_qrels, load_queries
TOK = re.compile(r"[a-z0-9]+")
_stem = PorterStemmer().stem
_cache = {}
def analyze(text):
out = []
for t in TOK.findall(text.lower()):
if t in ENGLISH_STOP_WORDS:
continue
s = _cache.get(t)
if s is None:
s = _cache[t] = _stem(t)
out.append(s)
return out
def run(name, split="test", depth=100, k1=0.9, b=0.4, max_q=None, seed=0):
t0 = time.time()
ids, titles, texts = load_corpus(name)
docs = [(ti + " " + tx).strip() for ti, tx in zip(titles, texts)]
queries = load_queries(name)
qrels = load_qrels(name, split)
qids = sorted(q for q in qrels if q in queries)
if max_q and len(qids) > max_q:
qids = sorted(np.random.default_rng(seed).choice(qids, max_q, replace=False).tolist())
cv = CountVectorizer(analyzer=analyze)
X = cv.fit_transform(docs).tocsc().astype(np.float32) # docs x terms
N = X.shape[0]
df = np.diff(X.indptr)
idf = np.log(1 + (N - df + 0.5) / (df + 0.5)).astype(np.float32)
X = X.tocsr()
dl = np.asarray(X.sum(1)).ravel()
norm = k1 * (1 - b + b * dl / dl.mean())
X = X.tocoo()
w = X.data * (k1 + 1) / (X.data + norm[X.row]) * idf[X.col]
W = sp.csc_matrix((w, (X.row, X.col)), shape=X.shape)
vocab = cv.vocabulary_
pools, run_ = {}, {}
for qid in qids:
terms = [vocab[t] for t in analyze(queries[qid]) if t in vocab]
if not terms:
s = np.zeros(N, dtype=np.float32)
else:
u, c = np.unique(terms, return_counts=True)
s = np.asarray(W[:, u] @ c.astype(np.float32)).ravel()
top = np.argpartition(-s, depth)[:depth]
top = top[np.argsort(-s[top], kind="stable")]
pools[qid] = {"ids": [ids[i] for i in top], "bm25": s[top].tolist()}
run_[qid] = {ids[i]: float(s[i]) for i in top}
metrics, _ = evaluate(run_, qrels)
os.makedirs(OUT, exist_ok=True)
json.dump(pools, open(os.path.join(OUT, f"{name}.{split}.pools.json"), "w"))
print(f"{name}/{split}: {N} docs, {len(qids)} queries, {time.time()-t0:.0f}s " +
" ".join(f"{k}={v:.4f}" for k, v in metrics.items()), flush=True)
if __name__ == "__main__":
for n in DATASETS:
run(n, "test")
for n, sp_ in [("scifact", "train"), ("nfcorpus", "dev"), ("fiqa", "dev")]:
run(n, sp_, max_q=200)