flashrag-dev 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flashrag/__init__.py +0 -0
- flashrag/config/__init__.py +2 -0
- flashrag/config/config.py +219 -0
- flashrag/dataset/__init__.py +2 -0
- flashrag/dataset/dataset.py +160 -0
- flashrag/dataset/utils.py +60 -0
- flashrag/evaluator/__init__.py +2 -0
- flashrag/evaluator/_bleu.py +209 -0
- flashrag/evaluator/evaluator.py +82 -0
- flashrag/evaluator/metrics.py +508 -0
- flashrag/evaluator/utils.py +19 -0
- flashrag/generator/__init__.py +2 -0
- flashrag/generator/fid.py +247 -0
- flashrag/generator/generator.py +623 -0
- flashrag/generator/openai_generator.py +96 -0
- flashrag/generator/stop_word_criteria.py +105 -0
- flashrag/judger/__init__.py +1 -0
- flashrag/judger/judger.py +184 -0
- flashrag/pipeline/__init__.py +3 -0
- flashrag/pipeline/active_pipeline.py +980 -0
- flashrag/pipeline/branching_pipeline.py +250 -0
- flashrag/pipeline/pipeline.py +248 -0
- flashrag/pipeline/replug_utils.py +249 -0
- flashrag/prompt/__init__.py +1 -0
- flashrag/prompt/base_prompt.py +165 -0
- flashrag/prompt/selfask_examplars.py +108 -0
- flashrag/prompt/trace_examplars.py +4015 -0
- flashrag/refiner/__init__.py +2 -0
- flashrag/refiner/kg_refiner.py +612 -0
- flashrag/refiner/llmlingua_compressor.py +2360 -0
- flashrag/refiner/refiner.py +252 -0
- flashrag/refiner/selective_context_compressor.py +294 -0
- flashrag/retriever/__init__.py +3 -0
- flashrag/retriever/__main__.py +4 -0
- flashrag/retriever/encoder.py +120 -0
- flashrag/retriever/index_builder.py +334 -0
- flashrag/retriever/reranker.py +145 -0
- flashrag/retriever/retriever.py +346 -0
- flashrag/retriever/utils.py +49 -0
- flashrag/utils/__init__.py +2 -0
- flashrag/utils/constants.py +51 -0
- flashrag/utils/pred_parse.py +25 -0
- flashrag/utils/utils.py +135 -0
- flashrag_dev-0.1.1.dist-info/LICENSE +21 -0
- flashrag_dev-0.1.1.dist-info/METADATA +616 -0
- flashrag_dev-0.1.1.dist-info/RECORD +48 -0
- flashrag_dev-0.1.1.dist-info/WHEEL +5 -0
- flashrag_dev-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import faiss
|
|
3
|
+
import json
|
|
4
|
+
import warnings
|
|
5
|
+
import numpy as np
|
|
6
|
+
from typing import cast
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
import argparse
|
|
10
|
+
import datasets
|
|
11
|
+
import torch
|
|
12
|
+
from tqdm import tqdm
|
|
13
|
+
from flashrag.retriever.utils import load_model, load_corpus, pooling
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Index_Builder:
|
|
17
|
+
r"""A tool class used to build an index used in retrieval."""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
retrieval_method,
|
|
22
|
+
model_path,
|
|
23
|
+
corpus_path,
|
|
24
|
+
save_dir,
|
|
25
|
+
max_length,
|
|
26
|
+
batch_size,
|
|
27
|
+
use_fp16,
|
|
28
|
+
pooling_method,
|
|
29
|
+
faiss_type=None,
|
|
30
|
+
embedding_path=None,
|
|
31
|
+
save_embedding=False,
|
|
32
|
+
faiss_gpu=False,
|
|
33
|
+
use_sentence_transformer=False,
|
|
34
|
+
bm25_backend='bm25s'
|
|
35
|
+
):
|
|
36
|
+
|
|
37
|
+
self.retrieval_method = retrieval_method.lower()
|
|
38
|
+
self.model_path = model_path
|
|
39
|
+
self.corpus_path = corpus_path
|
|
40
|
+
self.save_dir = save_dir
|
|
41
|
+
self.max_length = max_length
|
|
42
|
+
self.batch_size = batch_size
|
|
43
|
+
self.use_fp16 = use_fp16
|
|
44
|
+
self.pooling_method = pooling_method
|
|
45
|
+
self.faiss_type = faiss_type if faiss_type is not None else "Flat"
|
|
46
|
+
self.embedding_path = embedding_path
|
|
47
|
+
self.save_embedding = save_embedding
|
|
48
|
+
self.faiss_gpu = faiss_gpu
|
|
49
|
+
self.use_sentence_transformer = use_sentence_transformer
|
|
50
|
+
self.bm25_backend = bm25_backend
|
|
51
|
+
|
|
52
|
+
self.gpu_num = torch.cuda.device_count()
|
|
53
|
+
# prepare save dir
|
|
54
|
+
print(self.save_dir)
|
|
55
|
+
if not os.path.exists(self.save_dir):
|
|
56
|
+
os.makedirs(self.save_dir)
|
|
57
|
+
else:
|
|
58
|
+
if not self._check_dir(self.save_dir):
|
|
59
|
+
warnings.warn("Some files already exists in save dir and may be overwritten.", UserWarning)
|
|
60
|
+
|
|
61
|
+
self.index_save_path = os.path.join(self.save_dir, f"{self.retrieval_method}_{self.faiss_type}.index")
|
|
62
|
+
|
|
63
|
+
self.embedding_save_path = os.path.join(self.save_dir, f"emb_{self.retrieval_method}.memmap")
|
|
64
|
+
|
|
65
|
+
self.corpus = load_corpus(self.corpus_path)
|
|
66
|
+
|
|
67
|
+
print("Finish loading...")
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def _check_dir(dir_path):
|
|
71
|
+
r"""Check if the dir path exists and if there is content."""
|
|
72
|
+
|
|
73
|
+
if os.path.isdir(dir_path):
|
|
74
|
+
if len(os.listdir(dir_path)) > 0:
|
|
75
|
+
return False
|
|
76
|
+
else:
|
|
77
|
+
os.makedirs(dir_path, exist_ok=True)
|
|
78
|
+
return True
|
|
79
|
+
|
|
80
|
+
def build_index(self):
|
|
81
|
+
r"""Constructing different indexes based on selective retrieval method."""
|
|
82
|
+
if self.retrieval_method == "bm25":
|
|
83
|
+
if self.bm25_backend == 'pyserini':
|
|
84
|
+
self.build_bm25_index_pyserini()
|
|
85
|
+
elif self.bm25_backend == 'bm25s':
|
|
86
|
+
self.build_bm25_index_bm25s()
|
|
87
|
+
else:
|
|
88
|
+
assert False, "Invalid bm25 backend!"
|
|
89
|
+
else:
|
|
90
|
+
self.build_dense_index()
|
|
91
|
+
|
|
92
|
+
def build_bm25_index_pyserini(self):
|
|
93
|
+
"""Building BM25 index based on Pyserini library.
|
|
94
|
+
|
|
95
|
+
Reference: https://github.com/castorini/pyserini/blob/master/docs/usage-index.md#building-a-bm25-index-direct-java-implementation
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
# to use pyserini pipeline, we first need to place jsonl file in the folder
|
|
99
|
+
self.save_dir = os.path.join(self.save_dir, "bm25")
|
|
100
|
+
os.makedirs(self.save_dir, exist_ok=True)
|
|
101
|
+
temp_dir = self.save_dir + "/temp"
|
|
102
|
+
temp_file_path = temp_dir + "/temp.jsonl"
|
|
103
|
+
os.makedirs(temp_dir)
|
|
104
|
+
shutil.copyfile(self.corpus_path, temp_file_path)
|
|
105
|
+
|
|
106
|
+
print("Start building bm25 index...")
|
|
107
|
+
pyserini_args = [
|
|
108
|
+
"--collection",
|
|
109
|
+
"JsonCollection",
|
|
110
|
+
"--input",
|
|
111
|
+
temp_dir,
|
|
112
|
+
"--index",
|
|
113
|
+
self.save_dir,
|
|
114
|
+
"--generator",
|
|
115
|
+
"DefaultLuceneDocumentGenerator",
|
|
116
|
+
"--threads",
|
|
117
|
+
"1",
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
subprocess.run(["python", "-m", "pyserini.index.lucene"] + pyserini_args)
|
|
121
|
+
|
|
122
|
+
shutil.rmtree(temp_dir)
|
|
123
|
+
|
|
124
|
+
print("Finish!")
|
|
125
|
+
|
|
126
|
+
def build_bm25_index_bm25s(self):
|
|
127
|
+
"""Building BM25 index based on bm25s library."""
|
|
128
|
+
|
|
129
|
+
import bm25s
|
|
130
|
+
|
|
131
|
+
self.save_dir = os.path.join(self.save_dir, 'bm25')
|
|
132
|
+
os.makedirs(self.save_dir, exist_ok=True)
|
|
133
|
+
|
|
134
|
+
corpus = datasets.load_dataset("json", data_files=self.corpus_path, split="train")
|
|
135
|
+
corpus_text = corpus['contents']
|
|
136
|
+
retriever = bm25s.BM25(corpus=corpus, backend='numba')
|
|
137
|
+
retriever.index(corpus_text)
|
|
138
|
+
retriever.save(self.save_dir,corpus=corpus)
|
|
139
|
+
|
|
140
|
+
print("Finish!")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _load_embedding(self, embedding_path, corpus_size, hidden_size):
|
|
144
|
+
all_embeddings = np.memmap(embedding_path, mode="r", dtype=np.float32).reshape(corpus_size, hidden_size)
|
|
145
|
+
return all_embeddings
|
|
146
|
+
|
|
147
|
+
def _save_embedding(self, all_embeddings):
|
|
148
|
+
memmap = np.memmap(self.embedding_save_path, shape=all_embeddings.shape, mode="w+", dtype=all_embeddings.dtype)
|
|
149
|
+
length = all_embeddings.shape[0]
|
|
150
|
+
# add in batch
|
|
151
|
+
save_batch_size = 10000
|
|
152
|
+
if length > save_batch_size:
|
|
153
|
+
for i in tqdm(range(0, length, save_batch_size), leave=False, desc="Saving Embeddings"):
|
|
154
|
+
j = min(i + save_batch_size, length)
|
|
155
|
+
memmap[i:j] = all_embeddings[i:j]
|
|
156
|
+
else:
|
|
157
|
+
memmap[:] = all_embeddings
|
|
158
|
+
|
|
159
|
+
def st_encode_all(self):
|
|
160
|
+
if self.gpu_num > 1:
|
|
161
|
+
print("Use multi gpu!")
|
|
162
|
+
self.batch_size = self.batch_size * self.gpu_num
|
|
163
|
+
|
|
164
|
+
sentence_list = [item["contents"] for item in self.corpus]
|
|
165
|
+
if self.retrieval_method == "e5":
|
|
166
|
+
sentence_list = [f"passage: {doc}" for doc in sentence_list]
|
|
167
|
+
all_embeddings = self.encoder.encode(sentence_list, batch_size=self.batch_size)
|
|
168
|
+
|
|
169
|
+
return all_embeddings
|
|
170
|
+
|
|
171
|
+
def encode_all(self):
|
|
172
|
+
if self.gpu_num > 1:
|
|
173
|
+
print("Use multi gpu!")
|
|
174
|
+
self.encoder = torch.nn.DataParallel(self.encoder)
|
|
175
|
+
self.batch_size = self.batch_size * self.gpu_num
|
|
176
|
+
|
|
177
|
+
all_embeddings = []
|
|
178
|
+
|
|
179
|
+
for start_idx in tqdm(range(0, len(self.corpus), self.batch_size), desc="Inference Embeddings:"):
|
|
180
|
+
batch_data = self.corpus[start_idx : start_idx + self.batch_size]["contents"]
|
|
181
|
+
|
|
182
|
+
if self.retrieval_method == "e5":
|
|
183
|
+
batch_data = [f"passage: {doc}" for doc in batch_data]
|
|
184
|
+
|
|
185
|
+
inputs = self.tokenizer(
|
|
186
|
+
batch_data,
|
|
187
|
+
padding=True,
|
|
188
|
+
truncation=True,
|
|
189
|
+
return_tensors="pt",
|
|
190
|
+
max_length=self.max_length,
|
|
191
|
+
).to("cuda")
|
|
192
|
+
|
|
193
|
+
inputs = {k: v.cuda() for k, v in inputs.items()}
|
|
194
|
+
|
|
195
|
+
# TODO: support encoder-only T5 model
|
|
196
|
+
if "T5" in type(self.encoder).__name__ or (self.gpu_num > 1 and "T5" in type(self.encoder.module).__name__):
|
|
197
|
+
# T5-based retrieval model
|
|
198
|
+
decoder_input_ids = torch.zeros((inputs["input_ids"].shape[0], 1), dtype=torch.long).to(
|
|
199
|
+
inputs["input_ids"].device
|
|
200
|
+
)
|
|
201
|
+
output = self.encoder(**inputs, decoder_input_ids=decoder_input_ids, return_dict=True)
|
|
202
|
+
embeddings = output.last_hidden_state[:, 0, :]
|
|
203
|
+
|
|
204
|
+
else:
|
|
205
|
+
output = self.encoder(**inputs, return_dict=True)
|
|
206
|
+
embeddings = pooling(
|
|
207
|
+
output.pooler_output, output.last_hidden_state, inputs["attention_mask"], self.pooling_method
|
|
208
|
+
)
|
|
209
|
+
if "dpr" not in self.retrieval_method:
|
|
210
|
+
embeddings = torch.nn.functional.normalize(embeddings, dim=-1)
|
|
211
|
+
|
|
212
|
+
embeddings = cast(torch.Tensor, embeddings)
|
|
213
|
+
embeddings = embeddings.detach().cpu().numpy()
|
|
214
|
+
all_embeddings.append(embeddings)
|
|
215
|
+
|
|
216
|
+
all_embeddings = np.concatenate(all_embeddings, axis=0)
|
|
217
|
+
all_embeddings = all_embeddings.astype(np.float32)
|
|
218
|
+
|
|
219
|
+
return all_embeddings
|
|
220
|
+
|
|
221
|
+
@torch.no_grad()
|
|
222
|
+
def build_dense_index(self):
|
|
223
|
+
"""Obtain the representation of documents based on the embedding model(BERT-based) and
|
|
224
|
+
construct a faiss index.
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
if os.path.exists(self.index_save_path):
|
|
228
|
+
print("The index file already exists and will be overwritten.")
|
|
229
|
+
|
|
230
|
+
if self.use_sentence_transformer:
|
|
231
|
+
from flashrag.retriever.encoder import STEncoder
|
|
232
|
+
|
|
233
|
+
self.encoder = STEncoder(
|
|
234
|
+
model_name=self.retrieval_method,
|
|
235
|
+
model_path=self.model_path,
|
|
236
|
+
max_length=self.max_length,
|
|
237
|
+
use_fp16=self.use_fp16,
|
|
238
|
+
)
|
|
239
|
+
hidden_size = self.encoder.model.get_sentence_embedding_dimension()
|
|
240
|
+
else:
|
|
241
|
+
self.encoder, self.tokenizer = load_model(model_path=self.model_path, use_fp16=self.use_fp16)
|
|
242
|
+
hidden_size = self.encoder.config.hidden_size
|
|
243
|
+
|
|
244
|
+
if self.embedding_path is not None:
|
|
245
|
+
corpus_size = len(self.corpus)
|
|
246
|
+
all_embeddings = self._load_embedding(self.embedding_path, corpus_size, hidden_size)
|
|
247
|
+
else:
|
|
248
|
+
all_embeddings = self.st_encode_all() if self.use_sentence_transformer else self.encode_all()
|
|
249
|
+
if self.save_embedding:
|
|
250
|
+
self._save_embedding(all_embeddings)
|
|
251
|
+
del self.corpus
|
|
252
|
+
|
|
253
|
+
# build index
|
|
254
|
+
print("Creating index")
|
|
255
|
+
dim = all_embeddings.shape[-1]
|
|
256
|
+
faiss_index = faiss.index_factory(dim, self.faiss_type, faiss.METRIC_INNER_PRODUCT)
|
|
257
|
+
|
|
258
|
+
if self.faiss_gpu:
|
|
259
|
+
co = faiss.GpuMultipleClonerOptions()
|
|
260
|
+
co.useFloat16 = True
|
|
261
|
+
co.shard = True
|
|
262
|
+
faiss_index = faiss.index_cpu_to_all_gpus(faiss_index, co)
|
|
263
|
+
if not faiss_index.is_trained:
|
|
264
|
+
faiss_index.train(all_embeddings)
|
|
265
|
+
faiss_index.add(all_embeddings)
|
|
266
|
+
faiss_index = faiss.index_gpu_to_cpu(faiss_index)
|
|
267
|
+
else:
|
|
268
|
+
if not faiss_index.is_trained:
|
|
269
|
+
faiss_index.train(all_embeddings)
|
|
270
|
+
faiss_index.add(all_embeddings)
|
|
271
|
+
|
|
272
|
+
faiss.write_index(faiss_index, self.index_save_path)
|
|
273
|
+
print("Finish!")
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
MODEL2POOLING = {"e5": "mean", "bge": "cls", "contriever": "mean", "jina": "mean"}
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def main():
|
|
280
|
+
parser = argparse.ArgumentParser(description="Creating index.")
|
|
281
|
+
|
|
282
|
+
# Basic parameters
|
|
283
|
+
parser.add_argument("--retrieval_method", type=str)
|
|
284
|
+
parser.add_argument("--model_path", type=str, default=None)
|
|
285
|
+
parser.add_argument("--corpus_path", type=str)
|
|
286
|
+
parser.add_argument("--save_dir", default="indexes/", type=str)
|
|
287
|
+
|
|
288
|
+
# Parameters for building dense index
|
|
289
|
+
parser.add_argument("--max_length", type=int, default=180)
|
|
290
|
+
parser.add_argument("--batch_size", type=int, default=512)
|
|
291
|
+
parser.add_argument("--use_fp16", default=False, action="store_true")
|
|
292
|
+
parser.add_argument("--pooling_method", type=str, default=None)
|
|
293
|
+
parser.add_argument("--faiss_type", default=None, type=str)
|
|
294
|
+
parser.add_argument("--embedding_path", default=None, type=str)
|
|
295
|
+
parser.add_argument("--save_embedding", action="store_true", default=False)
|
|
296
|
+
parser.add_argument("--faiss_gpu", default=False, action="store_true")
|
|
297
|
+
parser.add_argument("--sentence_transformer", action="store_true", default=False)
|
|
298
|
+
parser.add_argument("--bm25_backend", default='bm25s', choices=['bm25s','pyserini'])
|
|
299
|
+
|
|
300
|
+
args = parser.parse_args()
|
|
301
|
+
|
|
302
|
+
if args.pooling_method is None:
|
|
303
|
+
pooling_method = "mean"
|
|
304
|
+
for k, v in MODEL2POOLING.items():
|
|
305
|
+
if k in args.retrieval_method.lower():
|
|
306
|
+
pooling_method = v
|
|
307
|
+
break
|
|
308
|
+
else:
|
|
309
|
+
if args.pooling_method not in ["mean", "cls", "pooler"]:
|
|
310
|
+
raise NotImplementedError
|
|
311
|
+
else:
|
|
312
|
+
pooling_method = args.pooling_method
|
|
313
|
+
|
|
314
|
+
index_builder = Index_Builder(
|
|
315
|
+
retrieval_method=args.retrieval_method,
|
|
316
|
+
model_path=args.model_path,
|
|
317
|
+
corpus_path=args.corpus_path,
|
|
318
|
+
save_dir=args.save_dir,
|
|
319
|
+
max_length=args.max_length,
|
|
320
|
+
batch_size=args.batch_size,
|
|
321
|
+
use_fp16=args.use_fp16,
|
|
322
|
+
pooling_method=pooling_method,
|
|
323
|
+
faiss_type=args.faiss_type,
|
|
324
|
+
embedding_path=args.embedding_path,
|
|
325
|
+
save_embedding=args.save_embedding,
|
|
326
|
+
faiss_gpu=args.faiss_gpu,
|
|
327
|
+
use_sentence_transformer=args.sentence_transformer,
|
|
328
|
+
bm25_backend=args.bm25_backend
|
|
329
|
+
)
|
|
330
|
+
index_builder.build_index()
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
if __name__ == "__main__":
|
|
334
|
+
main()
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
import torch
|
|
3
|
+
import warnings
|
|
4
|
+
import numpy as np
|
|
5
|
+
from tqdm import tqdm
|
|
6
|
+
from transformers import AutoTokenizer, AutoModelForSequenceClassification
|
|
7
|
+
from flashrag.retriever.encoder import Encoder
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BaseReranker:
|
|
11
|
+
r"""Base object for all rerankers."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, config):
|
|
14
|
+
self.config = config
|
|
15
|
+
self.reranker_model_name = config["rerank_model_name"]
|
|
16
|
+
self.reranker_model_path = config["rerank_model_path"]
|
|
17
|
+
self.topk = config["rerank_topk"]
|
|
18
|
+
self.max_length = config["rerank_max_length"]
|
|
19
|
+
self.batch_size = config["rerank_batch_size"]
|
|
20
|
+
self.device = config["device"]
|
|
21
|
+
|
|
22
|
+
def get_rerank_scores(self, query_list: List[str], doc_list: List[str], batch_size):
|
|
23
|
+
"""Return flatten list of scores for each (query,doc) pair
|
|
24
|
+
Args:
|
|
25
|
+
query_list: List of N queries
|
|
26
|
+
doc_list: Nested list of length N, each element corresponds to K documents of a query
|
|
27
|
+
|
|
28
|
+
Return:
|
|
29
|
+
[score(q1,d1), score(q1,d2),... score(q2,d1),...]
|
|
30
|
+
"""
|
|
31
|
+
all_scores = []
|
|
32
|
+
return all_scores
|
|
33
|
+
|
|
34
|
+
@torch.inference_mode(mode=True)
|
|
35
|
+
def rerank(self, query_list, doc_list, batch_size=None, topk=None):
|
|
36
|
+
r"""Rerank doc_list."""
|
|
37
|
+
if batch_size is None:
|
|
38
|
+
batch_size = self.batch_size
|
|
39
|
+
if topk is None:
|
|
40
|
+
topk = self.topk
|
|
41
|
+
if isinstance(query_list, str):
|
|
42
|
+
query_list = [query_list]
|
|
43
|
+
if not isinstance(doc_list[0], list):
|
|
44
|
+
doc_list = [doc_list]
|
|
45
|
+
|
|
46
|
+
assert len(query_list) == len(doc_list)
|
|
47
|
+
if topk < min([len(docs) for docs in doc_list]):
|
|
48
|
+
warnings.warn("The number of doc returned by the retriever is less than the topk.")
|
|
49
|
+
|
|
50
|
+
# get doc contents
|
|
51
|
+
doc_contents = []
|
|
52
|
+
for docs in doc_list:
|
|
53
|
+
if all([isinstance(doc, str) for doc in docs]):
|
|
54
|
+
doc_contents.append([doc for doc in docs])
|
|
55
|
+
else:
|
|
56
|
+
doc_contents.append([doc["contents"] for doc in docs])
|
|
57
|
+
|
|
58
|
+
all_scores = self.get_rerank_scores(query_list, doc_contents, batch_size)
|
|
59
|
+
assert len(all_scores) == sum([len(docs) for docs in doc_list])
|
|
60
|
+
|
|
61
|
+
# sort docs
|
|
62
|
+
start_idx = 0
|
|
63
|
+
final_scores = []
|
|
64
|
+
final_docs = []
|
|
65
|
+
for docs in doc_list:
|
|
66
|
+
doc_scores = all_scores[start_idx : start_idx + len(docs)]
|
|
67
|
+
doc_scores = [float(score) for score in doc_scores]
|
|
68
|
+
sort_idxs = np.argsort(doc_scores)[::-1][:topk]
|
|
69
|
+
start_idx += len(docs)
|
|
70
|
+
|
|
71
|
+
final_docs.append([docs[idx] for idx in sort_idxs])
|
|
72
|
+
final_scores.append([doc_scores[idx] for idx in sort_idxs])
|
|
73
|
+
|
|
74
|
+
return final_docs, final_scores
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class CrossReranker(BaseReranker):
|
|
78
|
+
def __init__(self, config):
|
|
79
|
+
super().__init__(config)
|
|
80
|
+
self.tokenizer = AutoTokenizer.from_pretrained(self.reranker_model_path)
|
|
81
|
+
self.ranker = AutoModelForSequenceClassification.from_pretrained(self.reranker_model_path, num_labels=1)
|
|
82
|
+
self.ranker.eval()
|
|
83
|
+
self.ranker.to(self.device)
|
|
84
|
+
|
|
85
|
+
@torch.inference_mode(mode=True)
|
|
86
|
+
def get_rerank_scores(self, query_list, doc_list, batch_size):
|
|
87
|
+
# flatten all pairs
|
|
88
|
+
all_pairs = []
|
|
89
|
+
for query, docs in zip(query_list, doc_list):
|
|
90
|
+
all_pairs.extend([[query, doc] for doc in docs])
|
|
91
|
+
all_scores = []
|
|
92
|
+
for start_idx in tqdm(range(0, len(all_pairs), batch_size), desc="Reranking process: "):
|
|
93
|
+
pair_batch = all_pairs[start_idx : start_idx + batch_size]
|
|
94
|
+
|
|
95
|
+
inputs = self.tokenizer(
|
|
96
|
+
pair_batch, padding=True, truncation=True, return_tensors="pt", max_length=self.max_length
|
|
97
|
+
).to(self.device)
|
|
98
|
+
batch_scores = (
|
|
99
|
+
self.ranker(**inputs, return_dict=True)
|
|
100
|
+
.logits.view(
|
|
101
|
+
-1,
|
|
102
|
+
)
|
|
103
|
+
.float()
|
|
104
|
+
.cpu()
|
|
105
|
+
)
|
|
106
|
+
all_scores.extend(batch_scores)
|
|
107
|
+
|
|
108
|
+
return all_scores
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class BiReranker(BaseReranker):
|
|
112
|
+
def __init__(self, config):
|
|
113
|
+
super().__init__(config)
|
|
114
|
+
self.encoder = Encoder(
|
|
115
|
+
model_name=self.reranker_model_name,
|
|
116
|
+
model_path=self.reranker_model_path,
|
|
117
|
+
pooling_method=config["rerank_pooling_method"],
|
|
118
|
+
max_length=self.max_length,
|
|
119
|
+
use_fp16=config["rerank_use_fp16"],
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
def get_rerank_scores(self, query_list, doc_list, batch_size):
|
|
123
|
+
query_emb = []
|
|
124
|
+
for start_idx in range(0, len(query_list), batch_size):
|
|
125
|
+
query_batch = query_list[start_idx : start_idx + batch_size]
|
|
126
|
+
batch_emb = self.encoder.encode(query_batch, is_query=True)
|
|
127
|
+
query_emb.append(batch_emb)
|
|
128
|
+
query_emb = np.concatenate(query_emb, axis=0)
|
|
129
|
+
|
|
130
|
+
flat_doc_list = sum(doc_list, [])
|
|
131
|
+
doc_emb = []
|
|
132
|
+
for start_idx in range(0, len(flat_doc_list), batch_size):
|
|
133
|
+
doc_batch = flat_doc_list[start_idx : start_idx + batch_size]
|
|
134
|
+
batch_emb = self.encoder.encode(doc_batch, is_query=False)
|
|
135
|
+
doc_emb.append(batch_emb)
|
|
136
|
+
doc_emb = np.concatenate(doc_emb, axis=0)
|
|
137
|
+
|
|
138
|
+
scores = query_emb @ doc_emb.T # K*L
|
|
139
|
+
all_scores = []
|
|
140
|
+
score_idx = 0
|
|
141
|
+
for idx, doc in enumerate(doc_list):
|
|
142
|
+
all_scores.extend(scores[idx, score_idx : score_idx + len(doc)])
|
|
143
|
+
score_idx += len(doc)
|
|
144
|
+
|
|
145
|
+
return all_scores
|