Spaces:
Running on Zero
Running on Zero
Upload 14 files
Browse files- METHOD.md +32 -0
- README.md +44 -6
- app.py +133 -0
- benchmark/bm25_pools.py +75 -0
- benchmark/build_scores.py +39 -0
- benchmark/ce_stage.py +56 -0
- benchmark/common.py +60 -0
- benchmark/dense_stage.py +60 -0
- benchmark/mrr_recall.py +53 -0
- benchmark/run_eval.py +141 -0
- infomax_rerank.py +108 -0
- requirements.txt +6 -0
- results/beir_results.json +528 -0
- results/beir_results.md +75 -0
METHOD.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## Method
|
| 2 |
+
|
| 3 |
+
For one query with candidate passages $d_1,\dots,d_n$:
|
| 4 |
+
|
| 5 |
+
- $r_j$: cross-encoder logit (InfoMax) or z-score fusion of cross-encoder logit and dense cosine (InfoMax+F).
|
| 6 |
+
- $e_j$: all-MiniLM-L6-v2 embedding, centred on the pool mean and renormalised; $q$ the query embedding, treated the same way.
|
| 7 |
+
- $w_j = \mathrm{minmax}(r)_j^{\gamma}$: relevance weight.
|
| 8 |
+
|
| 9 |
+
**Target.** A latent query-information vector $z \in \mathbb{R}^r$, with prior $z \sim \mathcal N(0,\Lambda)$, on the aspect basis
|
| 10 |
+
$U = [\,q,\ u_2,\dots,u_r\,]$. Here $u_2,\dots,u_r$ are the top right-singular vectors of the relevance-weighted pool embeddings
|
| 11 |
+
after removing the query direction, and $\Lambda = \mathrm{diag}(1, \rho\, s_k^2/s_1^2)$.
|
| 12 |
+
|
| 13 |
+
**Observations.** Each passage is a noisy linear measurement of the target:
|
| 14 |
+
|
| 15 |
+
$$y_j = \sqrt{w_j}\,(U^\top e_j)^\top z + g_j^\top u + \varepsilon_j,\qquad g_j = (I-UU^\top)e_j .$$
|
| 16 |
+
|
| 17 |
+
The shared nuisance $u \sim \mathcal N(0,\beta I)$ correlates the noise of passages with similar off-query content (duplicates,
|
| 18 |
+
same source), and $\varepsilon_j \sim \mathcal N(0,\sigma^2)$.
|
| 19 |
+
|
| 20 |
+
**Selection.** Greedy maximisation of the conditional mutual information
|
| 21 |
+
|
| 22 |
+
$$I(z; y_j \mid y_S) = \tfrac12 \log \frac{\mathrm{Var}(y_j \mid y_S)}{\mathrm{Var}(y_j \mid y_S, z)},$$
|
| 23 |
+
|
| 24 |
+
computed exactly by Schur complements. No probabilities from a language model are needed: in the linear-Gaussian model the
|
| 25 |
+
posterior entropy depends only on which passages are selected.
|
| 26 |
+
|
| 27 |
+
**Anchoring.** The first pick is the top-1 of the base ranking $r$. At each later step, the base ranking's next item $b$ is replaced by
|
| 28 |
+
the max-gain item $j^\star$ only if $\mathrm{gain}(j^\star) > (1+\delta)\,\mathrm{gain}(b)$. With $\delta=\infty$ the selector returns
|
| 29 |
+
the base ranking exactly.
|
| 30 |
+
|
| 31 |
+
**Guarantee.** None is claimed. Correlated noise breaks submodularity of the objective, so the greedy rule has no worst-case
|
| 32 |
+
approximation bound.
|
README.md
CHANGED
|
@@ -1,15 +1,53 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
colorFrom: indigo
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.31.0
|
| 8 |
-
python_version: '3.12'
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
| 11 |
-
license:
|
| 12 |
short_description: Entropy-minimising passage reranker vs MS MARCO CE
|
| 13 |
---
|
| 14 |
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: InfoMax Reranker
|
| 3 |
+
emoji: 🔎
|
| 4 |
colorFrom: indigo
|
| 5 |
+
colorTo: blue
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.31.0
|
|
|
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
+
license: apache-2.0
|
| 11 |
short_description: Entropy-minimising passage reranker vs MS MARCO CE
|
| 12 |
---
|
| 13 |
|
| 14 |
+
# InfoMax Reranker
|
| 15 |
+
|
| 16 |
+
A greedy, information-maximising selector over a retrieved candidate pool, compared with the standard
|
| 17 |
+
`cross-encoder/ms-marco-MiniLM-L6-v2` reranker. Neural scoring runs on ZeroGPU; selection runs on CPU.
|
| 18 |
+
|
| 19 |
+
- `app.py`: Gradio app. Paste a query and passages separated by blank lines.
|
| 20 |
+
- `infomax_rerank.py`: the selectors: `infomax`, plus the baselines `mmr`, `fused`/`zfuse` and `by_score`.
|
| 21 |
+
- `METHOD.md`: the model and the selection rule.
|
| 22 |
+
- `results/`: BEIR test results (`beir_results.md`, `beir_results.json`).
|
| 23 |
+
- `benchmark/`: the scripts that produced them (see below).
|
| 24 |
+
|
| 25 |
+
## Hardware
|
| 26 |
+
|
| 27 |
+
Create the Space with the Gradio SDK and select **ZeroGPU** as hardware. Hosting a ZeroGPU Space needs a PRO or Enterprise
|
| 28 |
+
account. The `@spaces.GPU` decorator has no effect elsewhere, so the app also runs on CPU hardware and locally:
|
| 29 |
+
|
| 30 |
+
```
|
| 31 |
+
pip install -r requirements.txt gradio
|
| 32 |
+
python app.py
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
## Reproducing the BEIR numbers
|
| 36 |
+
|
| 37 |
+
The datasets are the standard BEIR zips (scifact, nfcorpus, fiqa, scidocs, trec-covid), unzipped into `$BEIR_DATA`.
|
| 38 |
+
Outputs go to `$EXP_OUT`. Models are loaded from local folders or Hub ids.
|
| 39 |
+
|
| 40 |
+
```
|
| 41 |
+
cd benchmark
|
| 42 |
+
export BEIR_DATA=/path/to/beir EXP_OUT=/path/to/out
|
| 43 |
+
python bm25_pools.py # BM25 top-100 pools: test splits + 200-query dev samples
|
| 44 |
+
python dense_stage.py sentence-transformers/all-MiniLM-L6-v2 50
|
| 45 |
+
python ce_stage.py cross-encoder/ms-marco-MiniLM-L6-v2 50
|
| 46 |
+
python build_scores.py 50
|
| 47 |
+
python run_eval.py # tune every method on dev splits only -> best_params.json
|
| 48 |
+
python run_eval.py --test # nDCG / Recall on the test splits + paired bootstrap CIs
|
| 49 |
+
python mrr_recall.py # MRR@10, Recall@3/5/10
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
As a check on the setup, BM25 nDCG@10 is within about 0.01 of the published BEIR BM25 numbers on all five datasets.
|
| 53 |
+
all-MiniLM-L6-v2 scores 0.648 nDCG@10 on SciFact over the full corpus.
|
app.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""InfoMax reranker demo (Gradio, ZeroGPU).
|
| 2 |
+
|
| 3 |
+
Neural scoring (MS MARCO MiniLM-L6 cross-encoder + all-MiniLM-L6-v2 embeddings) runs inside the
|
| 4 |
+
@spaces.GPU function; every selector then runs on CPU from those cached scores.
|
| 5 |
+
"""
|
| 6 |
+
import spaces # import first on ZeroGPU
|
| 7 |
+
|
| 8 |
+
import os
|
| 9 |
+
|
| 10 |
+
import gradio as gr
|
| 11 |
+
import numpy as np
|
| 12 |
+
import pandas as pd
|
| 13 |
+
import torch
|
| 14 |
+
from sentence_transformers import CrossEncoder, SentenceTransformer
|
| 15 |
+
|
| 16 |
+
from infomax_rerank import by_score, fused, infomax, mmr, zfuse
|
| 17 |
+
|
| 18 |
+
CE_ID = os.environ.get("CE_MODEL", "cross-encoder/ms-marco-MiniLM-L6-v2")
|
| 19 |
+
DENSE_ID = os.environ.get("DENSE_MODEL", "sentence-transformers/all-MiniLM-L6-v2")
|
| 20 |
+
ce = CrossEncoder(CE_ID, max_length=256, device="cpu")
|
| 21 |
+
enc = SentenceTransformer(DENSE_ID, device="cpu")
|
| 22 |
+
|
| 23 |
+
# Settings tuned on dev splits only (SciFact train, NFCorpus dev, FiQA dev); one setting for all datasets.
|
| 24 |
+
TUNED = {
|
| 25 |
+
"InfoMax": dict(rank=1, gamma=4.0, rho=0.3, sigma2=0.03, beta=0.0, delta=0.0),
|
| 26 |
+
"InfoMax+F": dict(rank=4, gamma=8.0, rho=1.0, sigma2=0.03, beta=3.0, delta=0.0, alpha=0.5),
|
| 27 |
+
"MMR": dict(lam=0.95),
|
| 28 |
+
"Fusion": dict(alpha=0.5),
|
| 29 |
+
}
|
| 30 |
+
METHODS = ["CE", "InfoMax", "InfoMax+F", "Dense", "MMR", "Fusion"]
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
@spaces.GPU(duration=60)
|
| 34 |
+
def neural_scores(query, passages):
|
| 35 |
+
dev = "cuda" if torch.cuda.is_available() else "cpu"
|
| 36 |
+
ce.model.to(dev)
|
| 37 |
+
enc.to(dev)
|
| 38 |
+
logits = np.asarray(ce.predict([(query, p) for p in passages], batch_size=64,
|
| 39 |
+
show_progress_bar=False), np.float32).reshape(-1)
|
| 40 |
+
E = enc.encode([query] + passages, batch_size=64, normalize_embeddings=True,
|
| 41 |
+
convert_to_numpy=True, show_progress_bar=False).astype(np.float32)
|
| 42 |
+
return logits, E[1:], E[0]
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def split_passages(text):
|
| 46 |
+
blocks = [b.strip() for b in text.replace("\r\n", "\n").split("\n\n")]
|
| 47 |
+
return [b for b in blocks if b]
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def rank_all(query, passages_text, k, rank, gamma, sigma2, beta, delta, alpha):
|
| 51 |
+
query = (query or "").strip()
|
| 52 |
+
passages = split_passages(passages_text or "")
|
| 53 |
+
if not query:
|
| 54 |
+
raise gr.Error("Enter a query.")
|
| 55 |
+
if len(passages) < 2:
|
| 56 |
+
raise gr.Error("Enter at least two passages, separated by a blank line.")
|
| 57 |
+
if len(passages) > 200:
|
| 58 |
+
raise gr.Error("At most 200 passages per request.")
|
| 59 |
+
k = int(min(k, len(passages)))
|
| 60 |
+
r, E, q = neural_scores(query, passages)
|
| 61 |
+
user_f = dict(rank=int(rank), gamma=gamma, rho=1.0, sigma2=sigma2, beta=beta,
|
| 62 |
+
delta=np.inf if delta >= 10 else delta)
|
| 63 |
+
orders, gains = {}, {}
|
| 64 |
+
orders["CE"] = by_score(r)
|
| 65 |
+
orders["Dense"] = by_score(E @ q)
|
| 66 |
+
orders["MMR"] = mmr(r, E, K=k, **TUNED["MMR"])
|
| 67 |
+
orders["Fusion"] = zfuse(r, E, q, **TUNED["Fusion"])
|
| 68 |
+
orders["InfoMax"], gains["InfoMax"] = infomax(r, E, q, K=k, return_gains=True, **TUNED["InfoMax"])
|
| 69 |
+
orders["InfoMax+F"], gains["InfoMax+F"] = infomax(fused(r, E, q, alpha), E, q, K=k, return_gains=True,
|
| 70 |
+
**user_f)
|
| 71 |
+
short = lambda i: f"[{i + 1}] " + (passages[i][:90] + ("…" if len(passages[i]) > 90 else ""))
|
| 72 |
+
table = pd.DataFrame({m: [short(i) for i in orders[m][:k]] for m in METHODS})
|
| 73 |
+
table.insert(0, "Rank", list(range(1, k + 1)))
|
| 74 |
+
detail = pd.DataFrame({
|
| 75 |
+
"Passage": [f"[{i + 1}]" for i in range(len(passages))],
|
| 76 |
+
"CE logit": np.round(r, 3),
|
| 77 |
+
"Dense cosine": np.round(E @ q, 3),
|
| 78 |
+
"InfoMax+F step": [orders["InfoMax+F"][:k].index(i) + 1 if i in orders["InfoMax+F"][:k] else "" for i in
|
| 79 |
+
range(len(passages))],
|
| 80 |
+
})
|
| 81 |
+
g = gains["InfoMax+F"]
|
| 82 |
+
note = ("**InfoMax+F information gain per step (nats):** " +
|
| 83 |
+
", ".join(f"{t + 1}: {v:.3f}" for t, v in enumerate(g)))
|
| 84 |
+
return table, detail, note
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
with open(os.path.join(os.path.dirname(__file__), "results", "beir_results.md")) as f:
|
| 88 |
+
RESULTS_MD = f.read()
|
| 89 |
+
|
| 90 |
+
EX_PASSAGES = """Metformin commonly causes gastrointestinal side effects such as nausea, diarrhoea and abdominal discomfort, especially when treatment starts.
|
| 91 |
+
|
| 92 |
+
Gastrointestinal upset, including nausea and diarrhoea, is the most frequent side effect reported by patients starting metformin.
|
| 93 |
+
|
| 94 |
+
Long-term metformin use is associated with reduced vitamin B12 absorption, so periodic B12 monitoring is recommended.
|
| 95 |
+
|
| 96 |
+
Lactic acidosis is a rare but serious adverse effect of metformin, mainly in patients with severe kidney impairment.
|
| 97 |
+
|
| 98 |
+
Metformin lowers hepatic glucose production and improves insulin sensitivity in type 2 diabetes.
|
| 99 |
+
|
| 100 |
+
Taking metformin with meals or using an extended-release formulation can reduce stomach upset.
|
| 101 |
+
|
| 102 |
+
Sulfonylureas such as glipizide can cause hypoglycaemia and weight gain."""
|
| 103 |
+
|
| 104 |
+
with gr.Blocks(title="InfoMax reranker") as demo:
|
| 105 |
+
gr.Markdown("# InfoMax reranker\nGreedy information-maximising selection over a candidate pool, compared with "
|
| 106 |
+
"the standard MS MARCO MiniLM-L6 cross-encoder. Paste a query and passages separated by blank lines.")
|
| 107 |
+
with gr.Tab("Rerank"):
|
| 108 |
+
with gr.Row():
|
| 109 |
+
with gr.Column(scale=1):
|
| 110 |
+
query = gr.Textbox(label="Query", value="What are the side effects of metformin?")
|
| 111 |
+
passages = gr.Textbox(label="Passages (blank line between passages)", lines=16, value=EX_PASSAGES)
|
| 112 |
+
k = gr.Slider(1, 20, value=5, step=1, label="K (passages to select)")
|
| 113 |
+
with gr.Accordion("InfoMax+F settings (defaults = dev-tuned)", open=False):
|
| 114 |
+
rank = gr.Slider(1, 16, value=4, step=1, label="Aspect rank r")
|
| 115 |
+
gamma = gr.Slider(0.5, 16, value=8.0, step=0.5, label="Relevance exponent γ")
|
| 116 |
+
sigma2 = gr.Slider(0.01, 1.0, value=0.03, step=0.01, label="Observation noise σ²")
|
| 117 |
+
beta = gr.Slider(0.0, 10.0, value=3.0, step=0.1, label="Shared nuisance variance β")
|
| 118 |
+
delta = gr.Slider(0.0, 10.0, value=0.0, step=0.1,
|
| 119 |
+
label="Anchor margin δ (10 = never leave the fused ranking)")
|
| 120 |
+
alpha = gr.Slider(0.0, 1.0, value=0.5, step=0.05, label="Dense weight α in relevance fusion")
|
| 121 |
+
btn = gr.Button("Rerank", variant="primary")
|
| 122 |
+
with gr.Column(scale=2):
|
| 123 |
+
out = gr.Dataframe(label="Top-K per method", wrap=True)
|
| 124 |
+
note = gr.Markdown()
|
| 125 |
+
detail = gr.Dataframe(label="Per-passage scores", wrap=True)
|
| 126 |
+
btn.click(rank_all, [query, passages, k, rank, gamma, sigma2, beta, delta, alpha], [out, detail, note])
|
| 127 |
+
with gr.Tab("BEIR results"):
|
| 128 |
+
gr.Markdown(RESULTS_MD)
|
| 129 |
+
with gr.Tab("Method"):
|
| 130 |
+
gr.Markdown(open(os.path.join(os.path.dirname(__file__), "METHOD.md")).read())
|
| 131 |
+
|
| 132 |
+
if __name__ == "__main__":
|
| 133 |
+
demo.queue().launch()
|
benchmark/bm25_pools.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""First stage: Lucene-style BM25 (k1=0.9, b=0.4, Porter stemming) top-100 pools per test query."""
|
| 2 |
+
import json
|
| 3 |
+
import os
|
| 4 |
+
import re
|
| 5 |
+
import time
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
import scipy.sparse as sp
|
| 9 |
+
from nltk.stem import PorterStemmer
|
| 10 |
+
from sklearn.feature_extraction.text import ENGLISH_STOP_WORDS, CountVectorizer
|
| 11 |
+
|
| 12 |
+
from common import DATASETS, OUT, evaluate, load_corpus, load_qrels, load_queries
|
| 13 |
+
|
| 14 |
+
TOK = re.compile(r"[a-z0-9]+")
|
| 15 |
+
_stem = PorterStemmer().stem
|
| 16 |
+
_cache = {}
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def analyze(text):
|
| 20 |
+
out = []
|
| 21 |
+
for t in TOK.findall(text.lower()):
|
| 22 |
+
if t in ENGLISH_STOP_WORDS:
|
| 23 |
+
continue
|
| 24 |
+
s = _cache.get(t)
|
| 25 |
+
if s is None:
|
| 26 |
+
s = _cache[t] = _stem(t)
|
| 27 |
+
out.append(s)
|
| 28 |
+
return out
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def run(name, split="test", depth=100, k1=0.9, b=0.4, max_q=None, seed=0):
|
| 32 |
+
t0 = time.time()
|
| 33 |
+
ids, titles, texts = load_corpus(name)
|
| 34 |
+
docs = [(ti + " " + tx).strip() for ti, tx in zip(titles, texts)]
|
| 35 |
+
queries = load_queries(name)
|
| 36 |
+
qrels = load_qrels(name, split)
|
| 37 |
+
qids = sorted(q for q in qrels if q in queries)
|
| 38 |
+
if max_q and len(qids) > max_q:
|
| 39 |
+
qids = sorted(np.random.default_rng(seed).choice(qids, max_q, replace=False).tolist())
|
| 40 |
+
cv = CountVectorizer(analyzer=analyze)
|
| 41 |
+
X = cv.fit_transform(docs).tocsc().astype(np.float32) # docs x terms
|
| 42 |
+
N = X.shape[0]
|
| 43 |
+
df = np.diff(X.indptr)
|
| 44 |
+
idf = np.log(1 + (N - df + 0.5) / (df + 0.5)).astype(np.float32)
|
| 45 |
+
X = X.tocsr()
|
| 46 |
+
dl = np.asarray(X.sum(1)).ravel()
|
| 47 |
+
norm = k1 * (1 - b + b * dl / dl.mean())
|
| 48 |
+
X = X.tocoo()
|
| 49 |
+
w = X.data * (k1 + 1) / (X.data + norm[X.row]) * idf[X.col]
|
| 50 |
+
W = sp.csc_matrix((w, (X.row, X.col)), shape=X.shape)
|
| 51 |
+
vocab = cv.vocabulary_
|
| 52 |
+
pools, run_ = {}, {}
|
| 53 |
+
for qid in qids:
|
| 54 |
+
terms = [vocab[t] for t in analyze(queries[qid]) if t in vocab]
|
| 55 |
+
if not terms:
|
| 56 |
+
s = np.zeros(N, dtype=np.float32)
|
| 57 |
+
else:
|
| 58 |
+
u, c = np.unique(terms, return_counts=True)
|
| 59 |
+
s = np.asarray(W[:, u] @ c.astype(np.float32)).ravel()
|
| 60 |
+
top = np.argpartition(-s, depth)[:depth]
|
| 61 |
+
top = top[np.argsort(-s[top], kind="stable")]
|
| 62 |
+
pools[qid] = {"ids": [ids[i] for i in top], "bm25": s[top].tolist()}
|
| 63 |
+
run_[qid] = {ids[i]: float(s[i]) for i in top}
|
| 64 |
+
metrics, _ = evaluate(run_, qrels)
|
| 65 |
+
os.makedirs(OUT, exist_ok=True)
|
| 66 |
+
json.dump(pools, open(os.path.join(OUT, f"{name}.{split}.pools.json"), "w"))
|
| 67 |
+
print(f"{name}/{split}: {N} docs, {len(qids)} queries, {time.time()-t0:.0f}s " +
|
| 68 |
+
" ".join(f"{k}={v:.4f}" for k, v in metrics.items()), flush=True)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
if __name__ == "__main__":
|
| 72 |
+
for n in DATASETS:
|
| 73 |
+
run(n, "test")
|
| 74 |
+
for n, sp_ in [("scifact", "train"), ("nfcorpus", "dev"), ("fiqa", "dev")]:
|
| 75 |
+
run(n, sp_, max_q=200)
|
benchmark/build_scores.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Merge pools + dense embeddings + cross-encoder logits into out/<tag>.scores.npz (format read by run_eval.py)."""
|
| 2 |
+
import json
|
| 3 |
+
import os
|
| 4 |
+
import sys
|
| 5 |
+
|
| 6 |
+
import numpy as np
|
| 7 |
+
|
| 8 |
+
from common import OUT
|
| 9 |
+
|
| 10 |
+
TAGS = ["scifact.test", "nfcorpus.test", "fiqa.test", "scidocs.test", "trec-covid.test",
|
| 11 |
+
"scifact.train", "nfcorpus.dev", "fiqa.dev"]
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def main(depth, tags):
|
| 15 |
+
for tag in tags:
|
| 16 |
+
name = tag.split(".")[0]
|
| 17 |
+
pools = json.load(open(os.path.join(OUT, f"{tag}.pools.json")))
|
| 18 |
+
z = np.load(os.path.join(OUT, f"{name}.emb.npz"))
|
| 19 |
+
emb = dict(zip(z["ids"].tolist(), z["emb"]))
|
| 20 |
+
zq = np.load(os.path.join(OUT, f"{name}.qemb.npz"))
|
| 21 |
+
qemb = dict(zip(zq["ids"].tolist(), zq["emb"]))
|
| 22 |
+
logits = np.load(os.path.join(OUT, f"{tag}.ce.npz"))["logits"]
|
| 23 |
+
qids = sorted(pools)
|
| 24 |
+
assert len(logits) == sum(len(pools[q]["ids"][:depth]) for q in qids), tag
|
| 25 |
+
out, p = {"qids": np.array(qids)}, 0
|
| 26 |
+
for q in qids:
|
| 27 |
+
ds = pools[q]["ids"][:depth]
|
| 28 |
+
out[f"{q}|ids"] = np.array(ds)
|
| 29 |
+
out[f"{q}|bm25"] = np.asarray(pools[q]["bm25"][:depth], np.float32)
|
| 30 |
+
out[f"{q}|ce"] = logits[p:p + len(ds)]
|
| 31 |
+
out[f"{q}|emb"] = np.stack([emb[d] for d in ds])
|
| 32 |
+
out[f"{q}|qemb"] = qemb[q]
|
| 33 |
+
p += len(ds)
|
| 34 |
+
np.savez_compressed(os.path.join(OUT, f"{tag}.scores.npz"), **out)
|
| 35 |
+
print(tag, len(qids), "queries")
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
if __name__ == "__main__":
|
| 39 |
+
main(int(sys.argv[1]), sys.argv[2:] or TAGS)
|
benchmark/ce_stage.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Cross-encoder stage: MS MARCO MiniLM-L6-v2 logits (max_length 256) for every (query, pool doc), top-DEPTH.
|
| 2 |
+
Resumable: out/<tag>.ce.npz holds logits computed so far in pool order.
|
| 3 |
+
Usage: python ce_stage.py CE_DIR DEPTH
|
| 4 |
+
"""
|
| 5 |
+
import json
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
import time
|
| 9 |
+
|
| 10 |
+
import numpy as np
|
| 11 |
+
import torch
|
| 12 |
+
from sentence_transformers import CrossEncoder
|
| 13 |
+
|
| 14 |
+
from common import OUT, load_corpus, load_queries
|
| 15 |
+
|
| 16 |
+
torch.set_num_threads(os.cpu_count())
|
| 17 |
+
TAGS = ["scifact.test", "nfcorpus.test", "trec-covid.test", "scifact.train", "nfcorpus.dev", "fiqa.dev",
|
| 18 |
+
"fiqa.test", "scidocs.test"]
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def main():
|
| 22 |
+
cdir, depth = sys.argv[1], int(sys.argv[2])
|
| 23 |
+
ce = CrossEncoder(cdir, max_length=256, device="cpu")
|
| 24 |
+
cache = {}
|
| 25 |
+
for tag in TAGS:
|
| 26 |
+
name = tag.split(".")[0]
|
| 27 |
+
if name not in cache:
|
| 28 |
+
cache.clear()
|
| 29 |
+
ids, titles, texts = load_corpus(name)
|
| 30 |
+
idx = {d: i for i, d in enumerate(ids)}
|
| 31 |
+
cache[name] = (idx, titles, texts, load_queries(name))
|
| 32 |
+
idx, titles, texts, queries = cache[name]
|
| 33 |
+
pools = json.load(open(os.path.join(OUT, f"{tag}.pools.json")))
|
| 34 |
+
pairs = [(queries[q], (titles[idx[d]] + ". " if titles[idx[d]] else "") + texts[idx[d]])
|
| 35 |
+
for q in sorted(pools) for d in pools[q]["ids"][:depth]]
|
| 36 |
+
path = os.path.join(OUT, f"{tag}.ce.npz")
|
| 37 |
+
done = np.load(path)["logits"] if os.path.exists(path) else np.zeros(0, np.float32)
|
| 38 |
+
if len(done) == len(pairs):
|
| 39 |
+
print(f"{tag}: complete", flush=True)
|
| 40 |
+
continue
|
| 41 |
+
t0, start = time.time(), len(done)
|
| 42 |
+
for s in range(start, len(pairs), 2048):
|
| 43 |
+
chunk = pairs[s:s + 2048]
|
| 44 |
+
o = np.argsort([len(a) + len(b) for a, b in chunk], kind="stable") # length-sort: less padding
|
| 45 |
+
out = np.asarray(ce.predict([chunk[i] for i in o], batch_size=32, show_progress_bar=False),
|
| 46 |
+
np.float32).reshape(-1)
|
| 47 |
+
res = np.empty_like(out)
|
| 48 |
+
res[o] = out
|
| 49 |
+
done = np.concatenate([done, res])
|
| 50 |
+
np.savez(path, logits=done)
|
| 51 |
+
rate = (len(done) - start) / (time.time() - t0)
|
| 52 |
+
print(f"{tag}: {len(done)}/{len(pairs)} pairs, {rate:.0f} pairs/s", flush=True)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
if __name__ == "__main__":
|
| 56 |
+
main()
|
benchmark/common.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Shared I/O and evaluation for the BEIR reranking experiments."""
|
| 2 |
+
import csv
|
| 3 |
+
import json
|
| 4 |
+
import os
|
| 5 |
+
|
| 6 |
+
import numpy as np
|
| 7 |
+
import pytrec_eval
|
| 8 |
+
|
| 9 |
+
DATA = os.environ.get("BEIR_DATA", "beir")
|
| 10 |
+
OUT = os.environ.get("EXP_OUT", "out")
|
| 11 |
+
DATASETS = ["scifact", "nfcorpus", "fiqa", "scidocs", "trec-covid"]
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def load_corpus(name):
|
| 15 |
+
ids, texts, titles = [], [], []
|
| 16 |
+
with open(os.path.join(DATA, name, "corpus.jsonl")) as f:
|
| 17 |
+
for line in f:
|
| 18 |
+
d = json.loads(line)
|
| 19 |
+
ids.append(d["_id"])
|
| 20 |
+
titles.append(d.get("title") or "")
|
| 21 |
+
texts.append(d.get("text") or "")
|
| 22 |
+
return ids, titles, texts
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def load_queries(name):
|
| 26 |
+
q = {}
|
| 27 |
+
with open(os.path.join(DATA, name, "queries.jsonl")) as f:
|
| 28 |
+
for line in f:
|
| 29 |
+
d = json.loads(line)
|
| 30 |
+
q[d["_id"]] = d["text"]
|
| 31 |
+
return q
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def load_qrels(name, split="test"):
|
| 35 |
+
qrels = {}
|
| 36 |
+
with open(os.path.join(DATA, name, "qrels", f"{split}.tsv")) as f:
|
| 37 |
+
r = csv.reader(f, delimiter="\t")
|
| 38 |
+
next(r)
|
| 39 |
+
for qid, did, score in r:
|
| 40 |
+
qrels.setdefault(qid, {})[did] = int(score)
|
| 41 |
+
return qrels
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def evaluate(run, qrels, ks=(3, 5, 10)):
|
| 45 |
+
"""run: {qid: {docid: score}}. Returns mean nDCG@k and Recall@k (BEIR/pytrec_eval conventions)."""
|
| 46 |
+
measures = {f"ndcg_cut.{','.join(map(str, ks))}", f"recall.{','.join(map(str, ks))}"}
|
| 47 |
+
q = {k: v for k, v in qrels.items() if k in run}
|
| 48 |
+
ev = pytrec_eval.RelevanceEvaluator(q, measures)
|
| 49 |
+
res = ev.evaluate({k: run[k] for k in q})
|
| 50 |
+
out = {}
|
| 51 |
+
for k in ks:
|
| 52 |
+
out[f"nDCG@{k}"] = float(np.mean([r[f"ndcg_cut_{k}"] for r in res.values()]))
|
| 53 |
+
out[f"R@{k}"] = float(np.mean([r[f"recall_{k}"] for r in res.values()]))
|
| 54 |
+
return out, res
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def order_to_run(order_ids):
|
| 58 |
+
"""Selected ordered list -> descending pseudo-scores so pytrec_eval keeps the order."""
|
| 59 |
+
n = len(order_ids)
|
| 60 |
+
return {d: float(n - i) for i, d in enumerate(order_ids)}
|
benchmark/dense_stage.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Dense stage: all-MiniLM-L6-v2 embeddings for every doc in the top-DEPTH pools and every query.
|
| 2 |
+
Resumable: per-dataset store out/<name>.emb.npz updated after each chunk.
|
| 3 |
+
Usage: python dense_stage.py MINILM_DIR DEPTH
|
| 4 |
+
"""
|
| 5 |
+
import json
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
import time
|
| 9 |
+
|
| 10 |
+
import numpy as np
|
| 11 |
+
import torch
|
| 12 |
+
from sentence_transformers import SentenceTransformer
|
| 13 |
+
|
| 14 |
+
from common import OUT, load_corpus, load_queries
|
| 15 |
+
|
| 16 |
+
torch.set_num_threads(os.cpu_count())
|
| 17 |
+
TAGS = ["scifact.test", "scifact.train", "nfcorpus.test", "nfcorpus.dev", "trec-covid.test",
|
| 18 |
+
"fiqa.test", "fiqa.dev", "scidocs.test"]
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def main():
|
| 22 |
+
mdir, depth = sys.argv[1], int(sys.argv[2])
|
| 23 |
+
enc = SentenceTransformer(mdir, device="cpu")
|
| 24 |
+
by_ds = {}
|
| 25 |
+
for t in TAGS:
|
| 26 |
+
by_ds.setdefault(t.split(".")[0], []).append(t)
|
| 27 |
+
for name, tags in by_ds.items():
|
| 28 |
+
store = os.path.join(OUT, f"{name}.emb.npz")
|
| 29 |
+
have = {}
|
| 30 |
+
if os.path.exists(store):
|
| 31 |
+
z = np.load(store)
|
| 32 |
+
have = dict(zip(z["ids"].tolist(), z["emb"]))
|
| 33 |
+
need, qneed = set(), set()
|
| 34 |
+
for t in tags:
|
| 35 |
+
pools = json.load(open(os.path.join(OUT, f"{t}.pools.json")))
|
| 36 |
+
for q, p in pools.items():
|
| 37 |
+
need.update(p["ids"][:depth])
|
| 38 |
+
qneed.add(q)
|
| 39 |
+
ids, titles, texts = load_corpus(name)
|
| 40 |
+
idx = {d: i for i, d in enumerate(ids)}
|
| 41 |
+
queries = load_queries(name)
|
| 42 |
+
todo = sorted(d for d in need if d not in have)
|
| 43 |
+
t0 = time.time()
|
| 44 |
+
for s in range(0, len(todo), 2048):
|
| 45 |
+
chunk = todo[s:s + 2048]
|
| 46 |
+
E = enc.encode([(titles[idx[d]] + ". " if titles[idx[d]] else "") + texts[idx[d]] for d in chunk],
|
| 47 |
+
batch_size=64, normalize_embeddings=True, convert_to_numpy=True, show_progress_bar=False)
|
| 48 |
+
have.update(zip(chunk, E.astype(np.float32)))
|
| 49 |
+
k = sorted(have)
|
| 50 |
+
np.savez(store, ids=np.array(k), emb=np.stack([have[d] for d in k]))
|
| 51 |
+
print(f"{name}: {min(s + 2048, len(todo))}/{len(todo)} docs, {time.time()-t0:.0f}s", flush=True)
|
| 52 |
+
qk = sorted(qneed)
|
| 53 |
+
QE = enc.encode([queries[q] for q in qk], batch_size=64, normalize_embeddings=True,
|
| 54 |
+
convert_to_numpy=True, show_progress_bar=False)
|
| 55 |
+
np.savez(os.path.join(OUT, f"{name}.qemb.npz"), ids=np.array(qk), emb=QE.astype(np.float32))
|
| 56 |
+
print(f"{name}: done ({len(have)} docs, {len(qk)} queries)", flush=True)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
if __name__ == "__main__":
|
| 60 |
+
main()
|
benchmark/mrr_recall.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""MRR@10 and Recall@{3,5,10} on the test splits for every method, using the dev-tuned settings in best_params.json.
|
| 2 |
+
Relevance = qrel grade > 0. Paired bootstrap 95% CIs for InfoMax+F minus CE and minus Fusion."""
|
| 3 |
+
import json
|
| 4 |
+
import os
|
| 5 |
+
|
| 6 |
+
import numpy as np
|
| 7 |
+
|
| 8 |
+
from common import OUT
|
| 9 |
+
from run_eval import TEST, boot_ci, load, method_orders
|
| 10 |
+
|
| 11 |
+
METHODS = ["BM25", "Dense", "CE", "MMR", "Fusion", "InfoMax", "InfoMax+F"]
|
| 12 |
+
best = {k: {kk: (np.inf if vv is None else vv) for kk, vv in v.items()}
|
| 13 |
+
for k, v in json.load(open(os.path.join(OUT, "best_params.json"))).items()}
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def per_query(Q, qrels, orders):
|
| 17 |
+
out = {"MRR@10": [], "R@3": [], "R@5": [], "R@10": []}
|
| 18 |
+
for x, o in zip(Q, orders):
|
| 19 |
+
rel = {d for d, g in qrels.get(x["qid"], {}).items() if g > 0}
|
| 20 |
+
if not rel:
|
| 21 |
+
continue
|
| 22 |
+
ranked = [x["ids"][j] for j in o]
|
| 23 |
+
hit = [d in rel for d in ranked[:10]]
|
| 24 |
+
out["MRR@10"].append(1.0 / (hit.index(True) + 1) if any(hit) else 0.0)
|
| 25 |
+
for k in (3, 5, 10):
|
| 26 |
+
out[f"R@{k}"].append(sum(hit[:k]) / len(rel))
|
| 27 |
+
return {k: np.array(v) for k, v in out.items()}
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
res = {}
|
| 31 |
+
for tag in TEST:
|
| 32 |
+
Q, qr = load(tag)
|
| 33 |
+
for m in METHODS:
|
| 34 |
+
o = method_orders(Q, m, best.get(m, {}))
|
| 35 |
+
res[(tag, m)] = per_query(Q, qr, o)
|
| 36 |
+
|
| 37 |
+
for metric in ("MRR@10", "R@3", "R@5", "R@10"):
|
| 38 |
+
print(f"\n{metric}")
|
| 39 |
+
print(f"{'dataset':16s}" + "".join(f"{m:>10s}" for m in METHODS))
|
| 40 |
+
for tag in TEST:
|
| 41 |
+
print(f"{tag:16s}" + "".join(f"{res[(tag, m)][metric].mean():10.4f}" for m in METHODS))
|
| 42 |
+
|
| 43 |
+
print("\nInfoMax+F minus CE / minus Fusion, paired bootstrap 95% CI")
|
| 44 |
+
for metric in ("MRR@10", "R@10"):
|
| 45 |
+
for tag in TEST:
|
| 46 |
+
a = res[(tag, "InfoMax+F")][metric]
|
| 47 |
+
s = []
|
| 48 |
+
for o in ("CE", "Fusion"):
|
| 49 |
+
d, lo, hi = boot_ci(a, res[(tag, o)][metric])
|
| 50 |
+
s.append(f"vs {o}: {d:+.4f} [{lo:+.4f},{hi:+.4f}]")
|
| 51 |
+
print(f"{metric:7s}{tag:16s}" + " ".join(s))
|
| 52 |
+
json.dump({f"{t}|{m}|{k}": float(v.mean()) for (t, m), d in res.items() for k, v in d.items()},
|
| 53 |
+
open(os.path.join(OUT, "mrr_recall.json"), "w"), indent=1)
|
benchmark/run_eval.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Tune every method's knobs on dev splits only (scifact.train, nfcorpus.dev, fiqa.dev; one global
|
| 2 |
+
setting per method), then evaluate on the five BEIR test splits.
|
| 3 |
+
|
| 4 |
+
Usage: python run_eval.py [--quick]
|
| 5 |
+
"""
|
| 6 |
+
import itertools
|
| 7 |
+
import json
|
| 8 |
+
import os
|
| 9 |
+
import sys
|
| 10 |
+
import time
|
| 11 |
+
|
| 12 |
+
import numpy as np
|
| 13 |
+
|
| 14 |
+
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
| 15 |
+
from common import OUT, evaluate, load_qrels, order_to_run # noqa: E402
|
| 16 |
+
from infomax_rerank import by_score, fused, infomax, mmr, zfuse # noqa: E402
|
| 17 |
+
|
| 18 |
+
DEV = ["scifact.train", "nfcorpus.dev", "fiqa.dev"]
|
| 19 |
+
TEST = ["scifact.test", "nfcorpus.test", "fiqa.test", "scidocs.test", "trec-covid.test"]
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def load(tag):
|
| 23 |
+
name, split = tag.split(".")
|
| 24 |
+
z = np.load(os.path.join(OUT, f"{tag}.scores.npz"))
|
| 25 |
+
qs = []
|
| 26 |
+
for q in z["qids"]:
|
| 27 |
+
qs.append({k: z[f"{q}|{k}"] for k in ("ids", "bm25", "ce", "emb", "qemb")} | {"qid": str(q)})
|
| 28 |
+
return qs, load_qrels(name, split)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def score(Q, qrels, orders):
|
| 32 |
+
run = {x["qid"]: order_to_run([x["ids"][j] for j in o]) for x, o in zip(Q, orders)}
|
| 33 |
+
m, per = evaluate(run, qrels)
|
| 34 |
+
return m, {q: v["ndcg_cut_10"] for q, v in per.items()}
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def method_orders(Q, method, p):
|
| 38 |
+
if method == "BM25":
|
| 39 |
+
return [by_score(x["bm25"]) for x in Q]
|
| 40 |
+
if method == "Dense":
|
| 41 |
+
return [by_score(x["emb"] @ x["qemb"]) for x in Q]
|
| 42 |
+
if method == "CE":
|
| 43 |
+
return [by_score(x["ce"]) for x in Q]
|
| 44 |
+
if method == "MMR":
|
| 45 |
+
return [mmr(x["ce"], x["emb"], **p) for x in Q]
|
| 46 |
+
if method == "Fusion":
|
| 47 |
+
return [zfuse(x["ce"], x["emb"], x["qemb"], **p) for x in Q]
|
| 48 |
+
if method == "InfoMax":
|
| 49 |
+
return [infomax(x["ce"], x["emb"], x["qemb"], **p) for x in Q]
|
| 50 |
+
if method == "InfoMax+F": # same selector, relevance/anchor = the tuned CE+dense fusion score
|
| 51 |
+
p = dict(p)
|
| 52 |
+
a = p.pop("alpha")
|
| 53 |
+
return [infomax(fused(x["ce"], x["emb"], x["qemb"], a), x["emb"], x["qemb"], **p) for x in Q]
|
| 54 |
+
raise ValueError(method)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
GRIDS = {
|
| 58 |
+
"MMR": [{"lam": l} for l in (0.5, 0.6, 0.7, 0.8, 0.9, 0.95)],
|
| 59 |
+
"Fusion": [{"alpha": a} for a in (0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9)],
|
| 60 |
+
"InfoMax": [dict(rank=r, gamma=g, rho=rh, sigma2=s, beta=b, delta=d)
|
| 61 |
+
for r, g, rh, s, b, d in itertools.product(
|
| 62 |
+
(1, 4, 16), (1.0, 2.0, 4.0), (0.3, 1.0), (0.03, 0.3), (0.0, 0.3, 3.0),
|
| 63 |
+
(0.0, 0.5, 2.0, np.inf))],
|
| 64 |
+
"InfoMax+F": [dict(rank=r, gamma=g, rho=1.0, sigma2=s, beta=b, delta=d)
|
| 65 |
+
for r, g, s, b, d in itertools.product(
|
| 66 |
+
(1, 4, 16), (1.0, 2.0, 4.0, 8.0), (0.03, 0.3), (0.0, 0.3, 3.0), (0.0, 0.5, 2.0, np.inf))],
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def boot_ci(a, b, n=10000, seed=0):
|
| 71 |
+
d = np.asarray(a) - np.asarray(b)
|
| 72 |
+
rng = np.random.default_rng(seed)
|
| 73 |
+
m = d[rng.integers(0, len(d), (n, len(d)))].mean(1)
|
| 74 |
+
return float(d.mean()), float(np.percentile(m, 2.5)), float(np.percentile(m, 97.5))
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def main():
|
| 78 |
+
quick = "--quick" in sys.argv
|
| 79 |
+
best = {}
|
| 80 |
+
bp = os.path.join(OUT, "best_params.json")
|
| 81 |
+
if "--test" in sys.argv:
|
| 82 |
+
best = {k: {kk: (np.inf if vv is None else vv) for kk, vv in v.items()}
|
| 83 |
+
for k, v in json.load(open(bp)).items()}
|
| 84 |
+
grids = {}
|
| 85 |
+
else:
|
| 86 |
+
dev = {t: load(t) for t in DEV}
|
| 87 |
+
grids = GRIDS
|
| 88 |
+
for meth, grid in grids.items():
|
| 89 |
+
if meth == "InfoMax+F":
|
| 90 |
+
grid = [p | {"alpha": best["Fusion"]["alpha"]} for p in grid]
|
| 91 |
+
if quick:
|
| 92 |
+
grid = grid[:4]
|
| 93 |
+
t0 = time.time()
|
| 94 |
+
res = []
|
| 95 |
+
for p in grid:
|
| 96 |
+
v = np.mean([score(Q, qr, method_orders(Q, meth, p))[0]["nDCG@10"] for Q, qr in dev.values()])
|
| 97 |
+
res.append((v, p))
|
| 98 |
+
res.sort(key=lambda t: -t[0])
|
| 99 |
+
best[meth] = res[0][1]
|
| 100 |
+
ce_dev = np.mean([score(Q, qr, method_orders(Q, "CE", {}))[0]["nDCG@10"] for Q, qr in dev.values()])
|
| 101 |
+
print(f"[tune] {meth}: best dev nDCG@10 {res[0][0]:.4f} (CE {ce_dev:.4f}) with {res[0][1]} "
|
| 102 |
+
f"[{len(grid)} settings, {time.time()-t0:.0f}s]", flush=True)
|
| 103 |
+
if meth.startswith("InfoMax"):
|
| 104 |
+
for v, p in res[:5]:
|
| 105 |
+
print(f" {v:.4f} {p}")
|
| 106 |
+
if "--test" not in sys.argv:
|
| 107 |
+
json.dump({k: {kk: (None if vv == np.inf else vv) for kk, vv in v.items()} for k, v in best.items()},
|
| 108 |
+
open(bp, "w"), indent=1)
|
| 109 |
+
return
|
| 110 |
+
|
| 111 |
+
methods = ["BM25", "Dense", "CE", "MMR", "Fusion", "InfoMax", "InfoMax+F"]
|
| 112 |
+
table, perq = {}, {}
|
| 113 |
+
for tag in TEST:
|
| 114 |
+
Q, qr = load(tag)
|
| 115 |
+
for meth in methods:
|
| 116 |
+
o = method_orders(Q, meth, best.get(meth, {}))
|
| 117 |
+
m, pq = score(Q, qr, o)
|
| 118 |
+
table[(tag, meth)] = m
|
| 119 |
+
perq[(tag, meth)] = pq
|
| 120 |
+
print("\nnDCG@10 on test (BM25 top-50 pools; knobs tuned on dev only)")
|
| 121 |
+
print(f"{'dataset':16s}" + "".join(f"{m:>9s}" for m in methods))
|
| 122 |
+
for tag in TEST:
|
| 123 |
+
print(f"{tag:16s}" + "".join(f"{table[(tag, m)]['nDCG@10']:9.4f}" for m in methods))
|
| 124 |
+
print("\nInfoMax+F minus X, nDCG@10, paired bootstrap 95% CI")
|
| 125 |
+
rows = {}
|
| 126 |
+
for tag in TEST:
|
| 127 |
+
qids = sorted(perq[(tag, "CE")])
|
| 128 |
+
for other in ("CE", "MMR", "Fusion"):
|
| 129 |
+
a = [perq[(tag, "InfoMax+F")][q] for q in qids]
|
| 130 |
+
b = [perq[(tag, other)][q] for q in qids]
|
| 131 |
+
rows[(tag, other)] = boot_ci(a, b)
|
| 132 |
+
print(f"{tag:16s}" + " ".join(f"vs {o}: {rows[(tag, o)][0]:+.4f} [{rows[(tag, o)][1]:+.4f},{rows[(tag, o)][2]:+.4f}]"
|
| 133 |
+
for o in ("CE", "MMR", "Fusion")))
|
| 134 |
+
json.dump({"best": {k: {kk: (None if vv == np.inf else vv) for kk, vv in v.items()} for k, v in best.items()},
|
| 135 |
+
"test": {f"{t}|{m}": v for (t, m), v in table.items()},
|
| 136 |
+
"ci_infomax_minus": {f"{t}|{o}": v for (t, o), v in rows.items()}},
|
| 137 |
+
open(os.path.join(OUT, "results.json"), "w"), indent=1)
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
if __name__ == "__main__":
|
| 141 |
+
main()
|
infomax_rerank.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Rerankers over a fixed candidate pool. Each returns a full ordering (indices into the pool).
|
| 2 |
+
|
| 3 |
+
Inputs per query: r = cross-encoder logits (n,), E = unit doc embeddings (n, d), q = unit query embedding (d,).
|
| 4 |
+
"""
|
| 5 |
+
import numpy as np
|
| 6 |
+
from scipy.linalg import cho_factor, cho_solve
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def minmax(x):
|
| 10 |
+
lo, hi = float(x.min()), float(x.max())
|
| 11 |
+
return (x - lo) / (hi - lo) if hi > lo + 1e-9 else np.zeros_like(x)
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def by_score(s):
|
| 15 |
+
return list(np.argsort(-s, kind="stable"))
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def complete(order, base):
|
| 19 |
+
seen = set(order)
|
| 20 |
+
return list(order) + [j for j in base if j not in seen]
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
# ---------------------------------------------------------------- baselines
|
| 24 |
+
def mmr(r, E, lam, K=10):
|
| 25 |
+
rel = minmax(r)
|
| 26 |
+
sim = E @ E.T
|
| 27 |
+
S, rem = [], set(range(len(r)))
|
| 28 |
+
for _ in range(min(K, len(r))):
|
| 29 |
+
if not S:
|
| 30 |
+
j = int(np.argmax(rel))
|
| 31 |
+
else:
|
| 32 |
+
red = sim[:, S].max(1)
|
| 33 |
+
cand = np.array(sorted(rem))
|
| 34 |
+
v = lam * rel[cand] - (1 - lam) * red[cand]
|
| 35 |
+
j = int(cand[np.argmax(v)])
|
| 36 |
+
S.append(j)
|
| 37 |
+
rem.discard(j)
|
| 38 |
+
return complete(S, by_score(r))
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def fused(r, E, q, alpha):
|
| 42 |
+
"""z-score fusion of cross-encoder logits and dense cosine; alpha = weight on dense."""
|
| 43 |
+
d = E @ q
|
| 44 |
+
z = lambda x: (x - x.mean()) / (x.std() + 1e-9)
|
| 45 |
+
return (1 - alpha) * z(r) + alpha * z(d)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def zfuse(r, E, q, alpha):
|
| 49 |
+
return by_score(fused(r, E, q, alpha))
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
# ---------------------------------------------------------------- information-max selector
|
| 53 |
+
def _condvar(M, S):
|
| 54 |
+
if not S:
|
| 55 |
+
return np.diag(M).copy()
|
| 56 |
+
c = cho_factor(M[np.ix_(S, S)], lower=True)
|
| 57 |
+
A = cho_solve(c, M[S, :])
|
| 58 |
+
return np.diag(M) - np.einsum("ij,ij->j", M[S, :], A)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def infomax(r, E, q, rank=4, gamma=2.0, rho=1.0, sigma2=0.1, beta=0.0, delta=np.inf, K=10,
|
| 62 |
+
return_gains=False):
|
| 63 |
+
"""Greedy I(z; y_j | y_S) with z = query-aspect latent and correlated (nuisance) noise.
|
| 64 |
+
|
| 65 |
+
y_j = s_j <U^T e_j, z> + <g_j, u> + eps_j, z ~ N(0, Lambda), u ~ N(0, beta I), eps ~ N(0, sigma2)
|
| 66 |
+
e_j : pool-centred unit embedding; U: [query direction, top relevance-weighted pool directions]
|
| 67 |
+
s_j^2: minmax(cross-encoder)^gamma ; g_j = (I - U U^T) e_j (off-aspect content -> shared noise)
|
| 68 |
+
Anchored to the cross-encoder order: the CE top remaining item b is replaced by the max-gain
|
| 69 |
+
item only if gain(j*) > (1 + delta) * gain(b). delta = inf reproduces the cross-encoder.
|
| 70 |
+
"""
|
| 71 |
+
n = len(r)
|
| 72 |
+
base = by_score(r)
|
| 73 |
+
w = minmax(r) ** gamma
|
| 74 |
+
mu = E.mean(0)
|
| 75 |
+
Ec = E - mu
|
| 76 |
+
Ec /= np.linalg.norm(Ec, axis=1, keepdims=True) + 1e-9
|
| 77 |
+
qc = q - mu
|
| 78 |
+
qc /= np.linalg.norm(qc) + 1e-9
|
| 79 |
+
U = [qc]
|
| 80 |
+
lam = [1.0]
|
| 81 |
+
if rank > 1:
|
| 82 |
+
Wr = np.sqrt(w)[:, None] * (Ec - np.outer(Ec @ qc, qc))
|
| 83 |
+
_, sv, Vt = np.linalg.svd(Wr, full_matrices=False)
|
| 84 |
+
m = min(rank - 1, len(sv))
|
| 85 |
+
U += list(Vt[:m])
|
| 86 |
+
lam += list(rho * (sv[:m] / (sv[0] + 1e-9)) ** 2)
|
| 87 |
+
U = np.array(U).T # d x r
|
| 88 |
+
Lam = np.diag(lam)
|
| 89 |
+
P = Ec @ U # n x r aspect coordinates
|
| 90 |
+
H = np.sqrt(w)[:, None] * P
|
| 91 |
+
G = Ec - P @ U.T # off-aspect residual
|
| 92 |
+
R = beta * (G @ G.T) + sigma2 * np.eye(n)
|
| 93 |
+
C = H @ Lam @ H.T + R
|
| 94 |
+
S, rem, gains_log = [], np.ones(n, bool), []
|
| 95 |
+
for t in range(min(K, n)):
|
| 96 |
+
g = 0.5 * np.log(np.maximum(_condvar(C, S), 1e-12) / np.maximum(_condvar(R, S), 1e-12))
|
| 97 |
+
b = next(j for j in base if rem[j])
|
| 98 |
+
if t == 0:
|
| 99 |
+
j = b
|
| 100 |
+
else:
|
| 101 |
+
gm = np.where(rem, g, -np.inf)
|
| 102 |
+
js = int(np.argmax(gm))
|
| 103 |
+
j = js if gm[js] > (1 + delta) * g[b] + 1e-12 else b
|
| 104 |
+
gains_log.append(float(g[j]))
|
| 105 |
+
S.append(j)
|
| 106 |
+
rem[j] = False
|
| 107 |
+
order = complete(S, base)
|
| 108 |
+
return (order, gains_log) if return_gains else order
|
requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch
|
| 2 |
+
sentence-transformers>=3.0
|
| 3 |
+
numpy
|
| 4 |
+
scipy
|
| 5 |
+
pandas
|
| 6 |
+
spaces
|
results/beir_results.json
ADDED
|
@@ -0,0 +1,528 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"tuned_params_dev_only": {
|
| 3 |
+
"MMR": {
|
| 4 |
+
"lam": 0.95
|
| 5 |
+
},
|
| 6 |
+
"Fusion": {
|
| 7 |
+
"alpha": 0.5
|
| 8 |
+
},
|
| 9 |
+
"InfoMax": {
|
| 10 |
+
"rank": 1,
|
| 11 |
+
"gamma": 4.0,
|
| 12 |
+
"rho": 0.3,
|
| 13 |
+
"sigma2": 0.03,
|
| 14 |
+
"beta": 0.0,
|
| 15 |
+
"delta": 0.0
|
| 16 |
+
},
|
| 17 |
+
"InfoMax+F": {
|
| 18 |
+
"rank": 4,
|
| 19 |
+
"gamma": 8.0,
|
| 20 |
+
"rho": 1.0,
|
| 21 |
+
"sigma2": 0.03,
|
| 22 |
+
"beta": 3.0,
|
| 23 |
+
"delta": 0.0,
|
| 24 |
+
"alpha": 0.5
|
| 25 |
+
}
|
| 26 |
+
},
|
| 27 |
+
"ndcg_recall_test": {
|
| 28 |
+
"scifact.test|BM25": {
|
| 29 |
+
"nDCG@3": 0.6431199582422674,
|
| 30 |
+
"R@3": 0.7109444444444445,
|
| 31 |
+
"nDCG@5": 0.6596725941258738,
|
| 32 |
+
"R@5": 0.7526111111111111,
|
| 33 |
+
"nDCG@10": 0.6789590258685955,
|
| 34 |
+
"R@10": 0.8077222222222222
|
| 35 |
+
},
|
| 36 |
+
"scifact.test|Dense": {
|
| 37 |
+
"nDCG@3": 0.6163483357076979,
|
| 38 |
+
"R@3": 0.6764444444444444,
|
| 39 |
+
"nDCG@5": 0.6494822542293248,
|
| 40 |
+
"R@5": 0.7552777777777778,
|
| 41 |
+
"nDCG@10": 0.6775772341008127,
|
| 42 |
+
"R@10": 0.8395555555555556
|
| 43 |
+
},
|
| 44 |
+
"scifact.test|CE": {
|
| 45 |
+
"nDCG@3": 0.6355722307788756,
|
| 46 |
+
"R@3": 0.6832777777777779,
|
| 47 |
+
"nDCG@5": 0.6632119878395338,
|
| 48 |
+
"R@5": 0.7501666666666666,
|
| 49 |
+
"nDCG@10": 0.6762940264571006,
|
| 50 |
+
"R@10": 0.7884444444444444
|
| 51 |
+
},
|
| 52 |
+
"scifact.test|MMR": {
|
| 53 |
+
"nDCG@3": 0.6386774537671235,
|
| 54 |
+
"R@3": 0.6916111111111112,
|
| 55 |
+
"nDCG@5": 0.6626760413062829,
|
| 56 |
+
"R@5": 0.7501666666666666,
|
| 57 |
+
"nDCG@10": 0.6737188913639833,
|
| 58 |
+
"R@10": 0.7817777777777778
|
| 59 |
+
},
|
| 60 |
+
"scifact.test|Fusion": {
|
| 61 |
+
"nDCG@3": 0.6710598597617542,
|
| 62 |
+
"R@3": 0.716,
|
| 63 |
+
"nDCG@5": 0.6884192327495369,
|
| 64 |
+
"R@5": 0.7627777777777777,
|
| 65 |
+
"nDCG@10": 0.7120976163008534,
|
| 66 |
+
"R@10": 0.8312222222222222
|
| 67 |
+
},
|
| 68 |
+
"scifact.test|InfoMax": {
|
| 69 |
+
"nDCG@3": 0.6530626071442321,
|
| 70 |
+
"R@3": 0.7107222222222221,
|
| 71 |
+
"nDCG@5": 0.6737190144089154,
|
| 72 |
+
"R@5": 0.7646111111111111,
|
| 73 |
+
"nDCG@10": 0.6948257751035408,
|
| 74 |
+
"R@10": 0.8252222222222222
|
| 75 |
+
},
|
| 76 |
+
"scifact.test|InfoMax+F": {
|
| 77 |
+
"nDCG@3": 0.6725576912860219,
|
| 78 |
+
"R@3": 0.7196111111111111,
|
| 79 |
+
"nDCG@5": 0.6932716623902917,
|
| 80 |
+
"R@5": 0.7727777777777777,
|
| 81 |
+
"nDCG@10": 0.7155111299403955,
|
| 82 |
+
"R@10": 0.8368888888888889
|
| 83 |
+
},
|
| 84 |
+
"nfcorpus.test|BM25": {
|
| 85 |
+
"nDCG@3": 0.3823382822268318,
|
| 86 |
+
"R@3": 0.10380628945641764,
|
| 87 |
+
"nDCG@5": 0.3584927187341618,
|
| 88 |
+
"R@5": 0.12331434900055137,
|
| 89 |
+
"nDCG@10": 0.3261466393934428,
|
| 90 |
+
"R@10": 0.1564336372752942
|
| 91 |
+
},
|
| 92 |
+
"nfcorpus.test|Dense": {
|
| 93 |
+
"nDCG@3": 0.39224081480653206,
|
| 94 |
+
"R@3": 0.1054475795114919,
|
| 95 |
+
"nDCG@5": 0.3673855429222189,
|
| 96 |
+
"R@5": 0.12987323687278512,
|
| 97 |
+
"nDCG@10": 0.329672569677747,
|
| 98 |
+
"R@10": 0.1573953366222301
|
| 99 |
+
},
|
| 100 |
+
"nfcorpus.test|CE": {
|
| 101 |
+
"nDCG@3": 0.4229396163803222,
|
| 102 |
+
"R@3": 0.11255578971537836,
|
| 103 |
+
"nDCG@5": 0.3853234004565943,
|
| 104 |
+
"R@5": 0.13310119029572776,
|
| 105 |
+
"nDCG@10": 0.3458646508247627,
|
| 106 |
+
"R@10": 0.1623880974154255
|
| 107 |
+
},
|
| 108 |
+
"nfcorpus.test|MMR": {
|
| 109 |
+
"nDCG@3": 0.4203315776968791,
|
| 110 |
+
"R@3": 0.11215193296551876,
|
| 111 |
+
"nDCG@5": 0.3833962466547408,
|
| 112 |
+
"R@5": 0.13296678402481363,
|
| 113 |
+
"nDCG@10": 0.34496455529201764,
|
| 114 |
+
"R@10": 0.16095020847144315
|
| 115 |
+
},
|
| 116 |
+
"nfcorpus.test|Fusion": {
|
| 117 |
+
"nDCG@3": 0.4338920998144631,
|
| 118 |
+
"R@3": 0.11740153719863461,
|
| 119 |
+
"nDCG@5": 0.39540063670679393,
|
| 120 |
+
"R@5": 0.13643880315760448,
|
| 121 |
+
"nDCG@10": 0.35128231451281,
|
| 122 |
+
"R@10": 0.16419803765699678
|
| 123 |
+
},
|
| 124 |
+
"nfcorpus.test|InfoMax": {
|
| 125 |
+
"nDCG@3": 0.4313256861056477,
|
| 126 |
+
"R@3": 0.11718381080918494,
|
| 127 |
+
"nDCG@5": 0.39456391244419725,
|
| 128 |
+
"R@5": 0.13520038656020075,
|
| 129 |
+
"nDCG@10": 0.3459015527645562,
|
| 130 |
+
"R@10": 0.16016548330124453
|
| 131 |
+
},
|
| 132 |
+
"nfcorpus.test|InfoMax+F": {
|
| 133 |
+
"nDCG@3": 0.4288736628251464,
|
| 134 |
+
"R@3": 0.11576876191681756,
|
| 135 |
+
"nDCG@5": 0.3937758274378537,
|
| 136 |
+
"R@5": 0.13596229095231047,
|
| 137 |
+
"nDCG@10": 0.3521629267149766,
|
| 138 |
+
"R@10": 0.1636849294853285
|
| 139 |
+
},
|
| 140 |
+
"fiqa.test|BM25": {
|
| 141 |
+
"nDCG@3": 0.2128203105525299,
|
| 142 |
+
"R@3": 0.19910159285159282,
|
| 143 |
+
"nDCG@5": 0.22466852535393944,
|
| 144 |
+
"R@5": 0.24186197889901592,
|
| 145 |
+
"nDCG@10": 0.24313170715385354,
|
| 146 |
+
"R@10": 0.29682660018308166
|
| 147 |
+
},
|
| 148 |
+
"fiqa.test|Dense": {
|
| 149 |
+
"nDCG@3": 0.3238282228795522,
|
| 150 |
+
"R@3": 0.29630720359887025,
|
| 151 |
+
"nDCG@5": 0.3319682762608849,
|
| 152 |
+
"R@5": 0.3457364780281447,
|
| 153 |
+
"nDCG@10": 0.3454552348536008,
|
| 154 |
+
"R@10": 0.3935277171388282
|
| 155 |
+
},
|
| 156 |
+
"fiqa.test|CE": {
|
| 157 |
+
"nDCG@3": 0.2999096832508026,
|
| 158 |
+
"R@3": 0.28151820316172166,
|
| 159 |
+
"nDCG@5": 0.30844500797059193,
|
| 160 |
+
"R@5": 0.3263454052805905,
|
| 161 |
+
"nDCG@10": 0.3223933548101685,
|
| 162 |
+
"R@10": 0.3740349885025811
|
| 163 |
+
},
|
| 164 |
+
"fiqa.test|MMR": {
|
| 165 |
+
"nDCG@3": 0.29802765556267957,
|
| 166 |
+
"R@3": 0.2794605899929974,
|
| 167 |
+
"nDCG@5": 0.3072794992153618,
|
| 168 |
+
"R@5": 0.3258438620707139,
|
| 169 |
+
"nDCG@10": 0.3208304753270173,
|
| 170 |
+
"R@10": 0.3724917786260379
|
| 171 |
+
},
|
| 172 |
+
"fiqa.test|Fusion": {
|
| 173 |
+
"nDCG@3": 0.33832130422615464,
|
| 174 |
+
"R@3": 0.3066681432422173,
|
| 175 |
+
"nDCG@5": 0.3422595845439949,
|
| 176 |
+
"R@5": 0.3483966779105668,
|
| 177 |
+
"nDCG@10": 0.35845454570372476,
|
| 178 |
+
"R@10": 0.40362110026461884
|
| 179 |
+
},
|
| 180 |
+
"fiqa.test|InfoMax": {
|
| 181 |
+
"nDCG@3": 0.31712114265211333,
|
| 182 |
+
"R@3": 0.29967654516265624,
|
| 183 |
+
"nDCG@5": 0.32570617362477383,
|
| 184 |
+
"R@5": 0.35026567653882473,
|
| 185 |
+
"nDCG@10": 0.33689988451188513,
|
| 186 |
+
"R@10": 0.39294472674102304
|
| 187 |
+
},
|
| 188 |
+
"fiqa.test|InfoMax+F": {
|
| 189 |
+
"nDCG@3": 0.3378956061977511,
|
| 190 |
+
"R@3": 0.3066577326993994,
|
| 191 |
+
"nDCG@5": 0.34026758610658164,
|
| 192 |
+
"R@5": 0.3445141021529911,
|
| 193 |
+
"nDCG@10": 0.35772802740748577,
|
| 194 |
+
"R@10": 0.4016650873132354
|
| 195 |
+
},
|
| 196 |
+
"scidocs.test|BM25": {
|
| 197 |
+
"nDCG@3": 0.13953650967789885,
|
| 198 |
+
"R@3": 0.07848333333333334,
|
| 199 |
+
"nDCG@5": 0.12325782261102586,
|
| 200 |
+
"R@5": 0.10913333333333335,
|
| 201 |
+
"nDCG@10": 0.1489871672519904,
|
| 202 |
+
"R@10": 0.15653333333333336
|
| 203 |
+
},
|
| 204 |
+
"scidocs.test|Dense": {
|
| 205 |
+
"nDCG@3": 0.19532992037642224,
|
| 206 |
+
"R@3": 0.11063333333333332,
|
| 207 |
+
"nDCG@5": 0.17028354531362982,
|
| 208 |
+
"R@5": 0.15068333333333334,
|
| 209 |
+
"nDCG@10": 0.19872779902999924,
|
| 210 |
+
"R@10": 0.20253333333333337
|
| 211 |
+
},
|
| 212 |
+
"scidocs.test|CE": {
|
| 213 |
+
"nDCG@3": 0.1587600182121037,
|
| 214 |
+
"R@3": 0.08903333333333333,
|
| 215 |
+
"nDCG@5": 0.13789532380163394,
|
| 216 |
+
"R@5": 0.12068333333333334,
|
| 217 |
+
"nDCG@10": 0.16390846021956673,
|
| 218 |
+
"R@10": 0.16848333333333335
|
| 219 |
+
},
|
| 220 |
+
"scidocs.test|MMR": {
|
| 221 |
+
"nDCG@3": 0.15687177247920608,
|
| 222 |
+
"R@3": 0.08763333333333334,
|
| 223 |
+
"nDCG@5": 0.13595627703935984,
|
| 224 |
+
"R@5": 0.11843333333333333,
|
| 225 |
+
"nDCG@10": 0.16267991567191226,
|
| 226 |
+
"R@10": 0.16768333333333335
|
| 227 |
+
},
|
| 228 |
+
"scidocs.test|Fusion": {
|
| 229 |
+
"nDCG@3": 0.18745280547233126,
|
| 230 |
+
"R@3": 0.10563333333333334,
|
| 231 |
+
"nDCG@5": 0.16246159640953142,
|
| 232 |
+
"R@5": 0.1428333333333333,
|
| 233 |
+
"nDCG@10": 0.1936768980934276,
|
| 234 |
+
"R@10": 0.19993333333333332
|
| 235 |
+
},
|
| 236 |
+
"scidocs.test|InfoMax": {
|
| 237 |
+
"nDCG@3": 0.177,
|
| 238 |
+
"R@3": 0.10263333333333333,
|
| 239 |
+
"nDCG@5": 0.15042113247744018,
|
| 240 |
+
"R@5": 0.13338333333333333,
|
| 241 |
+
"nDCG@10": 0.1777698710919696,
|
| 242 |
+
"R@10": 0.18303333333333335
|
| 243 |
+
},
|
| 244 |
+
"scidocs.test|InfoMax+F": {
|
| 245 |
+
"nDCG@3": 0.18716785439017192,
|
| 246 |
+
"R@3": 0.10523333333333333,
|
| 247 |
+
"nDCG@5": 0.16395062501621754,
|
| 248 |
+
"R@5": 0.14468333333333333,
|
| 249 |
+
"nDCG@10": 0.19440802729822262,
|
| 250 |
+
"R@10": 0.20011666666666666
|
| 251 |
+
},
|
| 252 |
+
"trec-covid.test|BM25": {
|
| 253 |
+
"nDCG@3": 0.726536063698862,
|
| 254 |
+
"R@3": 0.006129608693906591,
|
| 255 |
+
"nDCG@5": 0.6931832808434869,
|
| 256 |
+
"R@5": 0.009495638499031355,
|
| 257 |
+
"nDCG@10": 0.645142792459132,
|
| 258 |
+
"R@10": 0.017583242371784345
|
| 259 |
+
},
|
| 260 |
+
"trec-covid.test|Dense": {
|
| 261 |
+
"nDCG@3": 0.7977649146579519,
|
| 262 |
+
"R@3": 0.0068480237477636895,
|
| 263 |
+
"nDCG@5": 0.7729841377993071,
|
| 264 |
+
"R@5": 0.011004464313449034,
|
| 265 |
+
"nDCG@10": 0.7502476986052402,
|
| 266 |
+
"R@10": 0.021582899500449337
|
| 267 |
+
},
|
| 268 |
+
"trec-covid.test|CE": {
|
| 269 |
+
"nDCG@3": 0.7835752445892035,
|
| 270 |
+
"R@3": 0.006500822879976922,
|
| 271 |
+
"nDCG@5": 0.7809746080465918,
|
| 272 |
+
"R@5": 0.01090665623000931,
|
| 273 |
+
"nDCG@10": 0.7443051348613305,
|
| 274 |
+
"R@10": 0.020647277435126458
|
| 275 |
+
},
|
| 276 |
+
"trec-covid.test|MMR": {
|
| 277 |
+
"nDCG@3": 0.7872617974664727,
|
| 278 |
+
"R@3": 0.006422345220043002,
|
| 279 |
+
"nDCG@5": 0.7700725718513098,
|
| 280 |
+
"R@5": 0.01065642470462764,
|
| 281 |
+
"nDCG@10": 0.7379188447063662,
|
| 282 |
+
"R@10": 0.020387648913364725
|
| 283 |
+
},
|
| 284 |
+
"trec-covid.test|Fusion": {
|
| 285 |
+
"nDCG@3": 0.8546927872602277,
|
| 286 |
+
"R@3": 0.0070038606633723065,
|
| 287 |
+
"nDCG@5": 0.8204160562167376,
|
| 288 |
+
"R@5": 0.011299291451340282,
|
| 289 |
+
"nDCG@10": 0.7971106002234066,
|
| 290 |
+
"R@10": 0.022138455639211664
|
| 291 |
+
},
|
| 292 |
+
"trec-covid.test|InfoMax": {
|
| 293 |
+
"nDCG@3": 0.7682680318494312,
|
| 294 |
+
"R@3": 0.006316769874605324,
|
| 295 |
+
"nDCG@5": 0.7494877534030594,
|
| 296 |
+
"R@5": 0.010045793623235813,
|
| 297 |
+
"nDCG@10": 0.7130600737191856,
|
| 298 |
+
"R@10": 0.0193403737920575
|
| 299 |
+
},
|
| 300 |
+
"trec-covid.test|InfoMax+F": {
|
| 301 |
+
"nDCG@3": 0.8376536063698862,
|
| 302 |
+
"R@3": 0.007084200669747274,
|
| 303 |
+
"nDCG@5": 0.8241432621592721,
|
| 304 |
+
"R@5": 0.011455199871392525,
|
| 305 |
+
"nDCG@10": 0.7896132829384864,
|
| 306 |
+
"R@10": 0.021971457445884583
|
| 307 |
+
}
|
| 308 |
+
},
|
| 309 |
+
"mrr_recall_test": {
|
| 310 |
+
"scifact.test|BM25|MRR@10": 0.6436679894179895,
|
| 311 |
+
"scifact.test|BM25|R@3": 0.7109444444444445,
|
| 312 |
+
"scifact.test|BM25|R@5": 0.7526111111111111,
|
| 313 |
+
"scifact.test|BM25|R@10": 0.8077222222222222,
|
| 314 |
+
"scifact.test|Dense|MRR@10": 0.6298108465608465,
|
| 315 |
+
"scifact.test|Dense|R@3": 0.6764444444444444,
|
| 316 |
+
"scifact.test|Dense|R@5": 0.7552777777777778,
|
| 317 |
+
"scifact.test|Dense|R@10": 0.8395555555555556,
|
| 318 |
+
"scifact.test|CE|MRR@10": 0.6487526455026454,
|
| 319 |
+
"scifact.test|CE|R@3": 0.6832777777777779,
|
| 320 |
+
"scifact.test|CE|R@5": 0.7501666666666668,
|
| 321 |
+
"scifact.test|CE|R@10": 0.7884444444444444,
|
| 322 |
+
"scifact.test|MMR|MRR@10": 0.6475079365079364,
|
| 323 |
+
"scifact.test|MMR|R@3": 0.6916111111111112,
|
| 324 |
+
"scifact.test|MMR|R@5": 0.7501666666666668,
|
| 325 |
+
"scifact.test|MMR|R@10": 0.7817777777777778,
|
| 326 |
+
"scifact.test|Fusion|MRR@10": 0.6794523809523809,
|
| 327 |
+
"scifact.test|Fusion|R@3": 0.7160000000000001,
|
| 328 |
+
"scifact.test|Fusion|R@5": 0.7627777777777778,
|
| 329 |
+
"scifact.test|Fusion|R@10": 0.8312222222222222,
|
| 330 |
+
"scifact.test|InfoMax|MRR@10": 0.6590330687830689,
|
| 331 |
+
"scifact.test|InfoMax|R@3": 0.7107222222222223,
|
| 332 |
+
"scifact.test|InfoMax|R@5": 0.7646111111111111,
|
| 333 |
+
"scifact.test|InfoMax|R@10": 0.8252222222222222,
|
| 334 |
+
"scifact.test|InfoMax+F|MRR@10": 0.6815767195767196,
|
| 335 |
+
"scifact.test|InfoMax+F|R@3": 0.7196111111111111,
|
| 336 |
+
"scifact.test|InfoMax+F|R@5": 0.7727777777777778,
|
| 337 |
+
"scifact.test|InfoMax+F|R@10": 0.8368888888888889,
|
| 338 |
+
"nfcorpus.test|BM25|MRR@10": 0.5234642979999017,
|
| 339 |
+
"nfcorpus.test|BM25|R@3": 0.10380628945641764,
|
| 340 |
+
"nfcorpus.test|BM25|R@5": 0.12331434900055138,
|
| 341 |
+
"nfcorpus.test|BM25|R@10": 0.15643363727529416,
|
| 342 |
+
"nfcorpus.test|Dense|MRR@10": 0.5332927908005307,
|
| 343 |
+
"nfcorpus.test|Dense|R@3": 0.10544757951149188,
|
| 344 |
+
"nfcorpus.test|Dense|R@5": 0.12987323687278512,
|
| 345 |
+
"nfcorpus.test|Dense|R@10": 0.1573953366222301,
|
| 346 |
+
"nfcorpus.test|CE|MRR@10": 0.577046783625731,
|
| 347 |
+
"nfcorpus.test|CE|R@3": 0.11255578971537837,
|
| 348 |
+
"nfcorpus.test|CE|R@5": 0.13310119029572776,
|
| 349 |
+
"nfcorpus.test|CE|R@10": 0.16238809741542548,
|
| 350 |
+
"nfcorpus.test|MMR|MRR@10": 0.5772875816993465,
|
| 351 |
+
"nfcorpus.test|MMR|R@3": 0.11215193296551876,
|
| 352 |
+
"nfcorpus.test|MMR|R@5": 0.13296678402481366,
|
| 353 |
+
"nfcorpus.test|MMR|R@10": 0.16095020847144315,
|
| 354 |
+
"nfcorpus.test|Fusion|MRR@10": 0.5721055088702147,
|
| 355 |
+
"nfcorpus.test|Fusion|R@3": 0.11740153719863461,
|
| 356 |
+
"nfcorpus.test|Fusion|R@5": 0.13643880315760448,
|
| 357 |
+
"nfcorpus.test|Fusion|R@10": 0.16419803765699678,
|
| 358 |
+
"nfcorpus.test|InfoMax|MRR@10": 0.5800432453683229,
|
| 359 |
+
"nfcorpus.test|InfoMax|R@3": 0.11718381080918494,
|
| 360 |
+
"nfcorpus.test|InfoMax|R@5": 0.13520038656020075,
|
| 361 |
+
"nfcorpus.test|InfoMax|R@10": 0.1601654833012445,
|
| 362 |
+
"nfcorpus.test|InfoMax+F|MRR@10": 0.5687982210428031,
|
| 363 |
+
"nfcorpus.test|InfoMax+F|R@3": 0.11576876191681756,
|
| 364 |
+
"nfcorpus.test|InfoMax+F|R@5": 0.13596229095231044,
|
| 365 |
+
"nfcorpus.test|InfoMax+F|R@10": 0.1636849294853285,
|
| 366 |
+
"fiqa.test|BM25|MRR@10": 0.3025493582206545,
|
| 367 |
+
"fiqa.test|BM25|R@3": 0.19910159285159287,
|
| 368 |
+
"fiqa.test|BM25|R@5": 0.24186197889901592,
|
| 369 |
+
"fiqa.test|BM25|R@10": 0.2968266001830817,
|
| 370 |
+
"fiqa.test|Dense|MRR@10": 0.435741230648638,
|
| 371 |
+
"fiqa.test|Dense|R@3": 0.29630720359887025,
|
| 372 |
+
"fiqa.test|Dense|R@5": 0.34573647802814467,
|
| 373 |
+
"fiqa.test|Dense|R@10": 0.3935277171388283,
|
| 374 |
+
"fiqa.test|CE|MRR@10": 0.4014948314716833,
|
| 375 |
+
"fiqa.test|CE|R@3": 0.28151820316172166,
|
| 376 |
+
"fiqa.test|CE|R@5": 0.3263454052805905,
|
| 377 |
+
"fiqa.test|CE|R@10": 0.3740349885025811,
|
| 378 |
+
"fiqa.test|MMR|MRR@10": 0.4007728297080149,
|
| 379 |
+
"fiqa.test|MMR|R@3": 0.27946058999299744,
|
| 380 |
+
"fiqa.test|MMR|R@5": 0.32584386207071386,
|
| 381 |
+
"fiqa.test|MMR|R@10": 0.3724917786260379,
|
| 382 |
+
"fiqa.test|Fusion|MRR@10": 0.45054992161473634,
|
| 383 |
+
"fiqa.test|Fusion|R@3": 0.3066681432422173,
|
| 384 |
+
"fiqa.test|Fusion|R@5": 0.34839667791056683,
|
| 385 |
+
"fiqa.test|Fusion|R@10": 0.40362110026461884,
|
| 386 |
+
"fiqa.test|InfoMax|MRR@10": 0.41511427101704873,
|
| 387 |
+
"fiqa.test|InfoMax|R@3": 0.29967654516265624,
|
| 388 |
+
"fiqa.test|InfoMax|R@5": 0.35026567653882473,
|
| 389 |
+
"fiqa.test|InfoMax|R@10": 0.39294472674102304,
|
| 390 |
+
"fiqa.test|InfoMax+F|MRR@10": 0.4490440672153635,
|
| 391 |
+
"fiqa.test|InfoMax+F|R@3": 0.30665773269939933,
|
| 392 |
+
"fiqa.test|InfoMax+F|R@5": 0.34451410215299105,
|
| 393 |
+
"fiqa.test|InfoMax+F|R@10": 0.4016650873132354,
|
| 394 |
+
"scidocs.test|BM25|MRR@10": 0.26165238095238097,
|
| 395 |
+
"scidocs.test|BM25|R@3": 0.07848333333333334,
|
| 396 |
+
"scidocs.test|BM25|R@5": 0.10913333333333333,
|
| 397 |
+
"scidocs.test|BM25|R@10": 0.15653333333333336,
|
| 398 |
+
"scidocs.test|Dense|MRR@10": 0.3502642857142857,
|
| 399 |
+
"scidocs.test|Dense|R@3": 0.11063333333333332,
|
| 400 |
+
"scidocs.test|Dense|R@5": 0.15068333333333334,
|
| 401 |
+
"scidocs.test|Dense|R@10": 0.20253333333333337,
|
| 402 |
+
"scidocs.test|CE|MRR@10": 0.29335079365079364,
|
| 403 |
+
"scidocs.test|CE|R@3": 0.08903333333333333,
|
| 404 |
+
"scidocs.test|CE|R@5": 0.12068333333333334,
|
| 405 |
+
"scidocs.test|CE|R@10": 0.16848333333333335,
|
| 406 |
+
"scidocs.test|MMR|MRR@10": 0.29261507936507936,
|
| 407 |
+
"scidocs.test|MMR|R@3": 0.08763333333333333,
|
| 408 |
+
"scidocs.test|MMR|R@5": 0.11843333333333333,
|
| 409 |
+
"scidocs.test|MMR|R@10": 0.16768333333333335,
|
| 410 |
+
"scidocs.test|Fusion|MRR@10": 0.336834126984127,
|
| 411 |
+
"scidocs.test|Fusion|R@3": 0.10563333333333334,
|
| 412 |
+
"scidocs.test|Fusion|R@5": 0.14283333333333334,
|
| 413 |
+
"scidocs.test|Fusion|R@10": 0.19993333333333332,
|
| 414 |
+
"scidocs.test|InfoMax|MRR@10": 0.3088793650793651,
|
| 415 |
+
"scidocs.test|InfoMax|R@3": 0.10263333333333334,
|
| 416 |
+
"scidocs.test|InfoMax|R@5": 0.13338333333333333,
|
| 417 |
+
"scidocs.test|InfoMax|R@10": 0.18303333333333335,
|
| 418 |
+
"scidocs.test|InfoMax+F|MRR@10": 0.33797738095238095,
|
| 419 |
+
"scidocs.test|InfoMax+F|R@3": 0.10523333333333333,
|
| 420 |
+
"scidocs.test|InfoMax+F|R@5": 0.14468333333333333,
|
| 421 |
+
"scidocs.test|InfoMax+F|R@10": 0.20011666666666666,
|
| 422 |
+
"trec-covid.test|BM25|MRR@10": 0.8966666666666666,
|
| 423 |
+
"trec-covid.test|BM25|R@3": 0.006129608693906592,
|
| 424 |
+
"trec-covid.test|BM25|R@5": 0.009495638499031355,
|
| 425 |
+
"trec-covid.test|BM25|R@10": 0.017583242371784342,
|
| 426 |
+
"trec-covid.test|Dense|MRR@10": 0.9066666666666667,
|
| 427 |
+
"trec-covid.test|Dense|R@3": 0.0068480237477636895,
|
| 428 |
+
"trec-covid.test|Dense|R@5": 0.011004464313449036,
|
| 429 |
+
"trec-covid.test|Dense|R@10": 0.021582899500449337,
|
| 430 |
+
"trec-covid.test|CE|MRR@10": 0.8880000000000001,
|
| 431 |
+
"trec-covid.test|CE|R@3": 0.006500822879976923,
|
| 432 |
+
"trec-covid.test|CE|R@5": 0.01090665623000931,
|
| 433 |
+
"trec-covid.test|CE|R@10": 0.02064727743512646,
|
| 434 |
+
"trec-covid.test|MMR|MRR@10": 0.8973333333333333,
|
| 435 |
+
"trec-covid.test|MMR|R@3": 0.006422345220043003,
|
| 436 |
+
"trec-covid.test|MMR|R@5": 0.010656424704627639,
|
| 437 |
+
"trec-covid.test|MMR|R@10": 0.020387648913364732,
|
| 438 |
+
"trec-covid.test|Fusion|MRR@10": 0.95,
|
| 439 |
+
"trec-covid.test|Fusion|R@3": 0.007003860663372309,
|
| 440 |
+
"trec-covid.test|Fusion|R@5": 0.011299291451340282,
|
| 441 |
+
"trec-covid.test|Fusion|R@10": 0.022138455639211668,
|
| 442 |
+
"trec-covid.test|InfoMax|MRR@10": 0.8983333333333333,
|
| 443 |
+
"trec-covid.test|InfoMax|R@3": 0.006316769874605324,
|
| 444 |
+
"trec-covid.test|InfoMax|R@5": 0.010045793623235811,
|
| 445 |
+
"trec-covid.test|InfoMax|R@10": 0.0193403737920575,
|
| 446 |
+
"trec-covid.test|InfoMax+F|MRR@10": 0.9533333333333335,
|
| 447 |
+
"trec-covid.test|InfoMax+F|R@3": 0.007084200669747275,
|
| 448 |
+
"trec-covid.test|InfoMax+F|R@5": 0.011455199871392525,
|
| 449 |
+
"trec-covid.test|InfoMax+F|R@10": 0.02197145744588459
|
| 450 |
+
},
|
| 451 |
+
"ci95_infomaxF_minus_baseline_ndcg10": {
|
| 452 |
+
"scifact.test|CE": [
|
| 453 |
+
0.039217103483294935,
|
| 454 |
+
0.022909537675935246,
|
| 455 |
+
0.055961078282767175
|
| 456 |
+
],
|
| 457 |
+
"scifact.test|MMR": [
|
| 458 |
+
0.04179223857641219,
|
| 459 |
+
0.025205164715028892,
|
| 460 |
+
0.05874056517241845
|
| 461 |
+
],
|
| 462 |
+
"scifact.test|Fusion": [
|
| 463 |
+
0.0034135136395420356,
|
| 464 |
+
-0.0009101887412512667,
|
| 465 |
+
0.00797676953601148
|
| 466 |
+
],
|
| 467 |
+
"nfcorpus.test|CE": [
|
| 468 |
+
0.006298275890213895,
|
| 469 |
+
0.00014448374247222494,
|
| 470 |
+
0.012741372907464514
|
| 471 |
+
],
|
| 472 |
+
"nfcorpus.test|MMR": [
|
| 473 |
+
0.007198371422958963,
|
| 474 |
+
0.0009795059674302584,
|
| 475 |
+
0.013655280658479303
|
| 476 |
+
],
|
| 477 |
+
"nfcorpus.test|Fusion": [
|
| 478 |
+
0.0008806122021665923,
|
| 479 |
+
-0.0021193370946726207,
|
| 480 |
+
0.004199293800506931
|
| 481 |
+
],
|
| 482 |
+
"fiqa.test|CE": [
|
| 483 |
+
0.035334672597317214,
|
| 484 |
+
0.026161183183126945,
|
| 485 |
+
0.04471607186251431
|
| 486 |
+
],
|
| 487 |
+
"fiqa.test|MMR": [
|
| 488 |
+
0.03689755208046848,
|
| 489 |
+
0.02775011299883495,
|
| 490 |
+
0.04613706029287447
|
| 491 |
+
],
|
| 492 |
+
"fiqa.test|Fusion": [
|
| 493 |
+
-0.0007265182962389718,
|
| 494 |
+
-0.0034145826445293446,
|
| 495 |
+
0.0018706218114601444
|
| 496 |
+
],
|
| 497 |
+
"scidocs.test|CE": [
|
| 498 |
+
0.030499567078655924,
|
| 499 |
+
0.02536860036621575,
|
| 500 |
+
0.03573024236108956
|
| 501 |
+
],
|
| 502 |
+
"scidocs.test|MMR": [
|
| 503 |
+
0.03172811162631037,
|
| 504 |
+
0.02661644582553447,
|
| 505 |
+
0.0369284625702656
|
| 506 |
+
],
|
| 507 |
+
"scidocs.test|Fusion": [
|
| 508 |
+
0.0007311292047950231,
|
| 509 |
+
-0.0011978684641854693,
|
| 510 |
+
0.0027101133301523487
|
| 511 |
+
],
|
| 512 |
+
"trec-covid.test|CE": [
|
| 513 |
+
0.045308148077155753,
|
| 514 |
+
0.008178194738087384,
|
| 515 |
+
0.0853383899434483
|
| 516 |
+
],
|
| 517 |
+
"trec-covid.test|MMR": [
|
| 518 |
+
0.05169443823212016,
|
| 519 |
+
0.014252145682437763,
|
| 520 |
+
0.09173759710245157
|
| 521 |
+
],
|
| 522 |
+
"trec-covid.test|Fusion": [
|
| 523 |
+
-0.007497317284920146,
|
| 524 |
+
-0.021556051504264526,
|
| 525 |
+
0.006579707248007931
|
| 526 |
+
]
|
| 527 |
+
}
|
| 528 |
+
}
|
results/beir_results.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## BEIR results (test splits)
|
| 2 |
+
|
| 3 |
+
Setup: BM25 (k1=0.9, b=0.4, Porter) top-50 candidates per query; MS MARCO MiniLM-L6-v2 cross-encoder (max length 256);
|
| 4 |
+
all-MiniLM-L6-v2 embeddings. All knobs tuned once on dev splits only (SciFact train, NFCorpus dev, FiQA dev; 200 queries each)
|
| 5 |
+
and applied unchanged to all five test sets.
|
| 6 |
+
|
| 7 |
+
- **CE**: cross-encoder ranking (the standard reranker).
|
| 8 |
+
- **InfoMax**: information-maximising greedy selection, relevance = CE.
|
| 9 |
+
- **InfoMax+F**: same selector, relevance = z-score fusion of CE and dense cosine.
|
| 10 |
+
- **BM25 / Dense**: first-stage scores of the same pool (no reranker). **MMR**: CE relevance, dense redundancy, λ=0.95.
|
| 11 |
+
**Fusion**: z-score average of CE and dense cosine (α=0.5), no set selection.
|
| 12 |
+
|
| 13 |
+
### nDCG@10
|
| 14 |
+
|
| 15 |
+
| Dataset | BM25 | Dense | CE | MMR | Fusion | InfoMax | InfoMax+F |
|
| 16 |
+
|---|---|---|---|---|---|---|---|
|
| 17 |
+
| SciFact | 0.6790 | 0.6776 | 0.6763 | 0.6737 | 0.7121 | 0.6948 | **0.7155** |
|
| 18 |
+
| NFCorpus | 0.3261 | 0.3297 | 0.3459 | 0.3450 | 0.3513 | 0.3459 | **0.3522** |
|
| 19 |
+
| FiQA | 0.2431 | 0.3455 | 0.3224 | 0.3208 | **0.3585** | 0.3369 | 0.3577 |
|
| 20 |
+
| SciDocs | 0.1490 | **0.1987** | 0.1639 | 0.1627 | 0.1937 | 0.1778 | 0.1944 |
|
| 21 |
+
| TREC-COVID | 0.6451 | 0.7502 | 0.7443 | 0.7379 | **0.7971** | 0.7131 | 0.7896 |
|
| 22 |
+
|
| 23 |
+
### MRR@10
|
| 24 |
+
|
| 25 |
+
| Dataset | BM25 | Dense | CE | MMR | Fusion | InfoMax | InfoMax+F |
|
| 26 |
+
|---|---|---|---|---|---|---|---|
|
| 27 |
+
| SciFact | 0.6437 | 0.6298 | 0.6488 | 0.6475 | 0.6795 | 0.6590 | **0.6816** |
|
| 28 |
+
| NFCorpus | 0.5235 | 0.5333 | 0.5770 | 0.5773 | 0.5721 | **0.5800** | 0.5688 |
|
| 29 |
+
| FiQA | 0.3025 | 0.4357 | 0.4015 | 0.4008 | **0.4505** | 0.4151 | 0.4490 |
|
| 30 |
+
| SciDocs | 0.2617 | **0.3503** | 0.2934 | 0.2926 | 0.3368 | 0.3089 | 0.3380 |
|
| 31 |
+
| TREC-COVID | 0.8967 | 0.9067 | 0.8880 | 0.8973 | 0.9500 | 0.8983 | **0.9533** |
|
| 32 |
+
|
| 33 |
+
### Recall@3
|
| 34 |
+
|
| 35 |
+
| Dataset | BM25 | Dense | CE | MMR | Fusion | InfoMax | InfoMax+F |
|
| 36 |
+
|---|---|---|---|---|---|---|---|
|
| 37 |
+
| SciFact | 0.7109 | 0.6764 | 0.6833 | 0.6916 | 0.7160 | 0.7107 | **0.7196** |
|
| 38 |
+
| NFCorpus | 0.1038 | 0.1054 | 0.1126 | 0.1122 | **0.1174** | 0.1172 | 0.1158 |
|
| 39 |
+
| FiQA | 0.1991 | 0.2963 | 0.2815 | 0.2795 | **0.3067** | 0.2997 | 0.3067 |
|
| 40 |
+
| SciDocs | 0.0785 | **0.1106** | 0.0890 | 0.0876 | 0.1056 | 0.1026 | 0.1052 |
|
| 41 |
+
| TREC-COVID | 0.0061 | 0.0068 | 0.0065 | 0.0064 | 0.0070 | 0.0063 | **0.0071** |
|
| 42 |
+
|
| 43 |
+
### Recall@5
|
| 44 |
+
|
| 45 |
+
| Dataset | BM25 | Dense | CE | MMR | Fusion | InfoMax | InfoMax+F |
|
| 46 |
+
|---|---|---|---|---|---|---|---|
|
| 47 |
+
| SciFact | 0.7526 | 0.7553 | 0.7502 | 0.7502 | 0.7628 | 0.7646 | **0.7728** |
|
| 48 |
+
| NFCorpus | 0.1233 | 0.1299 | 0.1331 | 0.1330 | **0.1364** | 0.1352 | 0.1360 |
|
| 49 |
+
| FiQA | 0.2419 | 0.3457 | 0.3263 | 0.3258 | 0.3484 | **0.3503** | 0.3445 |
|
| 50 |
+
| SciDocs | 0.1091 | **0.1507** | 0.1207 | 0.1184 | 0.1428 | 0.1334 | 0.1447 |
|
| 51 |
+
| TREC-COVID | 0.0095 | 0.0110 | 0.0109 | 0.0107 | 0.0113 | 0.0100 | **0.0115** |
|
| 52 |
+
|
| 53 |
+
### Recall@10
|
| 54 |
+
|
| 55 |
+
| Dataset | BM25 | Dense | CE | MMR | Fusion | InfoMax | InfoMax+F |
|
| 56 |
+
|---|---|---|---|---|---|---|---|
|
| 57 |
+
| SciFact | 0.8077 | **0.8396** | 0.7884 | 0.7818 | 0.8312 | 0.8252 | 0.8369 |
|
| 58 |
+
| NFCorpus | 0.1564 | 0.1574 | 0.1624 | 0.1610 | **0.1642** | 0.1602 | 0.1637 |
|
| 59 |
+
| FiQA | 0.2968 | 0.3935 | 0.3740 | 0.3725 | **0.4036** | 0.3929 | 0.4017 |
|
| 60 |
+
| SciDocs | 0.1565 | **0.2025** | 0.1685 | 0.1677 | 0.1999 | 0.1830 | 0.2001 |
|
| 61 |
+
| TREC-COVID | 0.0176 | 0.0216 | 0.0206 | 0.0204 | **0.0221** | 0.0193 | 0.0220 |
|
| 62 |
+
|
| 63 |
+
### InfoMax+F minus baseline, nDCG@10, paired bootstrap 95% CI
|
| 64 |
+
|
| 65 |
+
| Dataset | vs CE | vs MMR | vs Fusion |
|
| 66 |
+
|---|---|---|---|
|
| 67 |
+
| SciFact | +0.0392 [+0.0229, +0.0560] | +0.0418 [+0.0252, +0.0587] | +0.0034 [-0.0009, +0.0080] |
|
| 68 |
+
| NFCorpus | +0.0063 [+0.0001, +0.0127] | +0.0072 [+0.0010, +0.0137] | +0.0009 [-0.0021, +0.0042] |
|
| 69 |
+
| FiQA | +0.0353 [+0.0262, +0.0447] | +0.0369 [+0.0278, +0.0461] | -0.0007 [-0.0034, +0.0019] |
|
| 70 |
+
| SciDocs | +0.0305 [+0.0254, +0.0357] | +0.0317 [+0.0266, +0.0369] | +0.0007 [-0.0012, +0.0027] |
|
| 71 |
+
| TREC-COVID | +0.0453 [+0.0082, +0.0853] | +0.0517 [+0.0143, +0.0917] | -0.0075 [-0.0216, +0.0066] |
|
| 72 |
+
|
| 73 |
+
**Reading.** InfoMax+F beats the cross-encoder on nDCG@10 on all five datasets (intervals above zero). It ties Fusion everywhere:
|
| 74 |
+
on these single-hop benchmarks the gain comes from adding dense relevance, not from set selection. nDCG does not penalise
|
| 75 |
+
redundant relevant passages. TREC-COVID recall is near zero by construction (~494 relevant documents per query).
|