Views
No views yet
GenericDataLoader:corpus.jsonl: contains one JSON string per line with _id, title and text.
{"_id": "1234", "title": "", "text": "some text"}queries.jsonl an _id and a text is required per JSON string per line.
{"_id": "5678", "text": "a question?"}qrels/dev.tsv: represents the relation between question (query-id) and correct answer (corpus-id). The score column is mandatory, but always 1
1234 5678 1qrels/train.tsv: Structure is identical to dev.tsvGenericDataLoader, it is also possible to use HFDataLoader.
In this case, a Huggingface dataset is loaded directly, i.e. no individual files have to be created manually.
Nevertheless, this approach also requires a specific structure.
Two dataset repositories are needed: one for queries and corpus and another for qrels.
In addition, specific subset names must be defined.
Overall, the effort is more extensive, because new datasets have to be created (and uploaded to Huggingface Datasets).
The variant presented here uses existing datasets that are only minimally adapted and thus offer maximum compatibility.1# mmarco_beir.py
2
3import json
4import os
5import urllib.request
6
7import datasets
8
9# see https://huggingface.co/datasets/unicamp-dl/mmarco for supported languages
10LANGUAGE = "german"
11# target directory containin BEIR (https://github.com/beir-cellar/beir) compatible files
12OUT_DIR = f"mmarco-google/{LANGUAGE}/"
13
14os.makedirs(OUT_DIR, exist_ok=True)
15
16# download google based collection/corpus translation of msmarco and write corpus.jsonl for BEIR compatibility
17mmarco_ds = datasets.load_dataset("unicamp-dl/mmarco", f"collection-{LANGUAGE}")
18with open(os.path.join(OUT_DIR, "corpus.jsonl"), "w", encoding="utf-8") as out_file:
19 for entry in mmarco_ds["collection"]:
20 entry = {"_id": str(entry["id"]), "title": "", "text": entry["text"]}
21 out_file.write(f'{json.dumps(entry, ensure_ascii=False)}\n')
22
23# # download google based queries translation of msmarco and write queries.jsonl for BEIR compatibility
24mmarco_ds = datasets.load_dataset("unicamp-dl/mmarco", f"queries-{LANGUAGE}")
25mmarco_ds = datasets.concatenate_datasets([mmarco_ds["train"], mmarco_ds["dev.full"]])
26with open(os.path.join(OUT_DIR, "queries.jsonl"), "w", encoding="utf-8") as out_file:
27 for entry in mmarco_ds:
28 entry = {"_id": str(entry["id"]), "text": entry["text"]}
29 out_file.write(f'{json.dumps(entry, ensure_ascii=False)}\n')
30
31QRELS_DIR = os.path.abspath(os.path.join(OUT_DIR, "../qrels/"))
32os.makedirs(QRELS_DIR, exist_ok=True)
33
34# download qrels from URL instead of HF dataset
35# note: qrels are language independent
36for link in ["https://huggingface.co/datasets/BeIR/msmarco-qrels/resolve/main/dev.tsv",
37 "https://huggingface.co/datasets/BeIR/msmarco-qrels/resolve/main/train.tsv"]:
38 urllib.request.urlretrieve(link, os.path.join(QRELS_DIR, os.path.basename(link)))We use the MSMARCO Hard Negatives File (Provided by Nils Reimers): https://sbert.net/datasets/msmarco-hard-negatives.jsonl.gz Negative passage are hard negative examples, that were mined using different dense embedding, cross-encoder methods and lexical search methods. Contains upto 50 negatives for each of the four retrieval systems: [bm25, msmarco-distilbert-base-tas-b, msmarco-MiniLM-L-6-v3, msmarco-distilbert-base-v3] Each positive and negative passage comes with a score from a Cross-Encoder (msmarco-MiniLM-L-6-v3). This allows denoising, i.e. removing false negative passages that are actually relevant for the query.
MarginMSELoss is based on the paper of Hofstätter et al. As for MultipleNegativesRankingLoss, we have triplets: (query, passage1, passage2). In contrast to MultipleNegativesRankingLoss, passage1 and passage2 do not have to be strictly positive/negative, both can be relevant or not relevant for a given query. We then compute the Cross-Encoder score for (query, passage1) and (query, passage2). We provide scores for 160 million such pairs in our msmarco-hard-negatives dataset. We then compute the distance: CE_distance = CEScore(query, passage1) - CEScore(query, passage2) For our bi-encoder training, we encode query, passage1, and passage2 into vector spaces and then measure the dot-product between (query, passage1) and (query, passage2). Again, we measure the distance: BE_distance = DotScore(query, passage1) - DotScore(query, passage2) We then want to ensure that the distance predicted by the bi-encoder is close to the distance predicted by the cross-encoder, i.e., we optimize the mean-squared error (MSE) between CE_distance and BE_distance. An advantage of MarginMSELoss compared to MultipleNegativesRankingLoss is that we don’t require a positive and negative passage. As mentioned before, MS MARCO is redundant, and many passages contain the same or similar content. With MarginMSELoss, we can train on two relevant passages without issues: In that case, the CE_distance will be smaller and we expect that our bi-encoder also puts both passages closer in the vector space. And disadvantage of MarginMSELoss is the slower training time: We need way more epochs to get good results. In MultipleNegativesRankingLoss, with a batch size of 64, we compare one query against 128 passages. With MarginMSELoss, we compare a query only against two passages.
GenericDataLoader:1import os
2from beir.datasets.data_loader import GenericDataLoader
3
4data_path = "./mmarco-google/german"
5qrels_path = os.path.abspath(os.path.join(data_path, "../qrels"))
6corpus, queries, _ = GenericDataLoader(data_folder=data_path, qrels_folder=qrels_path).load(split="train")| model | NDCG@1 | NDCG@10 | NDCG@100 | comment |
|---|---|---|---|---|
| bi-encoder_msmarco_bert-base_german (new) | 0.5300 🏆 | 0.7196 🏆 | 0.7360 🏆 | "OUR model" |
| deepset/gbert-base-germandpr-X_encoder | 0.4828 | 0.6970 | 0.7147 | "has two encoder models (one for queries and one for corpus), is SOTA approach" |
| distiluse-base-multilingual-cased-v1 | 0.4561 | 0.6347 | 0.6613 | "trained on 15 languages" |
| paraphrase-multilingual-mpnet-base-v2 | 0.4511 | 0.6328 | 0.6592 | "trained on huge corpus, support for 50+ languages" |
| distiluse-base-multilingual-cased-v2 | 0.4350 | 0.6103 | 0.6411 | "trained on 50+ languages" |
| sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 | 0.4168 | 0.5931 | 0.6237 | "trained on large corpus, support for 50+ languages" |
| svalabs/bi-electra-ms-marco-german-uncased | 0.3818 | 0.5663 | 0.5986 | "most similar to OUR model" |
| BM25 | 0.3196 | 0.5377 | 0.5740 | "lexical approach" |