Fix bug ranker: wrong lambda function (#1824)

* Fix bug ranker: wrong lambda function

The zip function used in line 110 intends to choose the logits array to be the key for the lambda function while it should be the first/second logit of the logit array which corresponds to the classification label (has_answer)

* Use label 1 as has_answer label

* generic ranker (add if-cond for logits vector shape)

* remove test code

* remove test code...

* add two_logits test case for ranker module.

* complete the documentation of ranker, support rankers with 1 or 2 logits as output
This commit is contained in:
KUNPENG GUO 2021-12-06 17:13:57 +01:00 committed by GitHub
parent 8b7b51f0f5
commit 160f81aaa3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 58 additions and 8 deletions

View File

@ -10,7 +10,6 @@ from haystack.schema import Document
from haystack.nodes.ranker import BaseRanker
from haystack.modeling.utils import initialize_device_settings
logger = logging.getLogger(__name__)
@ -19,7 +18,9 @@ class SentenceTransformersRanker(BaseRanker):
Sentence Transformer based pre-trained Cross-Encoder model for Document Re-ranking (https://huggingface.co/cross-encoder).
Re-Ranking can be used on top of a retriever to boost the performance for document search. This is particularly useful if the retriever has a high recall but is bad in sorting the documents by relevance.
SentenceTransformerRanker handles Cross-Encoder models that use a single logit as similarity score.
SentenceTransformerRanker handles Cross-Encoder models
- use a single logit as similarity score e.g. cross-encoder/ms-marco-MiniLM-L-12-v2
- use two output logits (no_answer, has_answer) e.g. deepset/gbert-base-germandpr-reranking
https://www.sbert.net/docs/pretrained-models/ce-msmarco.html#usage-with-transformers
| With a SentenceTransformersRanker, you can:
@ -33,6 +34,7 @@ class SentenceTransformersRanker(BaseRanker):
p.add_node(component=retriever, name="ESRetriever", inputs=["Query"])
p.add_node(component=ranker, name="Ranker", inputs=["ESRetriever"])
"""
def __init__(
self,
model_name_or_path: Union[str, Path],
@ -63,9 +65,11 @@ class SentenceTransformersRanker(BaseRanker):
self.devices = devices
else:
self.devices, _ = initialize_device_settings(use_cuda=use_gpu, multi_gpu=True)
self.transformer_model = AutoModelForSequenceClassification.from_pretrained(pretrained_model_name_or_path=model_name_or_path, revision=model_version)
self.transformer_model = AutoModelForSequenceClassification.from_pretrained(
pretrained_model_name_or_path=model_name_or_path, revision=model_version)
self.transformer_model.to(str(self.devices[0]))
self.transformer_tokenizer = AutoTokenizer.from_pretrained(pretrained_model_name_or_path=model_name_or_path, revision=model_version)
self.transformer_tokenizer = AutoTokenizer.from_pretrained(pretrained_model_name_or_path=model_name_or_path,
revision=model_version)
self.transformer_model.eval()
if len(self.devices) > 1:
@ -101,14 +105,20 @@ class SentenceTransformersRanker(BaseRanker):
features = self.transformer_tokenizer([query for doc in documents], [doc.content for doc in documents],
padding=True, truncation=True, return_tensors="pt").to(self.devices[0])
# SentenceTransformerRanker uses the logit as similarity score and not the classifier's probability of label "1"
# SentenceTransformerRanker uses:
# 1. the logit as similarity score/answerable classification
# 2. the logits as answerable classification (no_answer / has_answer)
# https://www.sbert.net/docs/pretrained-models/ce-msmarco.html#usage-with-transformers
with torch.no_grad():
similarity_scores = self.transformer_model(**features).logits
logits_dim = similarity_scores.shape[1] # [batch_size, logits_dim]
sorted_scores_and_documents = sorted(
zip(similarity_scores, documents), key=lambda similarity_document_tuple:
# assume the last element in logits represents the `has_answer` label
similarity_document_tuple[0][-1] if logits_dim >= 2 else similarity_document_tuple[0],
reverse=True)
# rank documents according to scores
sorted_scores_and_documents = sorted(zip(similarity_scores, documents),
key=lambda similarity_document_tuple: similarity_document_tuple[0],
reverse=True)
sorted_documents = [doc for _, doc in sorted_scores_and_documents]
return sorted_documents[:top_k]

View File

@ -333,6 +333,12 @@ def table_reader():
return TableReader(model_name_or_path="google/tapas-base-finetuned-wtq")
@pytest.fixture(scope="module")
def ranker_two_logits():
return SentenceTransformersRanker(
model_name_or_path="deepset/gbert-base-germandpr-reranking",
)
@pytest.fixture(scope="module")
def ranker():
return SentenceTransformersRanker(

View File

@ -35,3 +35,37 @@ def test_ranker(ranker):
]
results = ranker.predict(query=query, documents=docs)
assert results[0] == docs[4]
def test_ranker_two_logits(ranker_two_logits):
assert isinstance(ranker_two_logits, BaseRanker)
assert isinstance(ranker_two_logits, SentenceTransformersRanker)
query = "Welches ist das wichtigste Gebäude in Königsmund, das einen religiösen Hintergrund hat?"
docs = [
Document(
content="""Aaron Aaron (oder ; "Ahärôn") ist ein Prophet, Hohepriester und der Bruder von Moses in den abrahamitischen Religionen. Aaron ist ebenso wie sein Bruder Moses ausschließlich aus religiösen Texten wie der Bibel und dem Koran bekannt. Die hebräische Bibel berichtet, dass Aaron und seine ältere Schwester Mirjam im Gegensatz zu Mose, der am ägyptischen Königshof aufwuchs, bei ihren Verwandten im östlichen Grenzland Ägyptens (Goschen) blieben. Als Mose den ägyptischen König zum ersten Mal mit den Israeliten konfrontierte, fungierte Aaron als Sprecher ("Prophet") seines Bruders gegenüber dem Pharao. Ein Teil des Gesetzes (Tora), das Mose von""",
meta={"name": "0"},
id="1",
),
Document(
content="""Demokratische Republik Kongo im Süden. Die angolanische Hauptstadt Luanda liegt an der Atlantikküste im Nordwesten des Landes. Angola liegt zwar in einer tropischen Zone, hat aber ein Klima, das aufgrund des Zusammenwirkens von drei Faktoren nicht für diese Region typisch ist: So ist das Klima Angolas durch zwei Jahreszeiten gekennzeichnet: Regenfälle von Oktober bis April und die als "Cacimbo" bezeichnete Dürre von Mai bis August, die, wie der Name schon sagt, trockener ist und niedrigere Temperaturen aufweist. Andererseits sind die Niederschlagsmengen an der Küste sehr hoch und nehmen von Norden nach Süden und von Süden nach Süden ab, mit""",
id="2",
),
Document(
content="""Schopenhauer, indem er ihn als einen letztlich oberflächlichen Denker beschreibt: ""Schopenhauer hat einen ziemlich groben Verstand ... wo wirkliche Tiefe beginnt, hört seine auf."" Sein Freund Bertrand Russell hatte eine schlechte Meinung von dem Philosophen und griff ihn in seiner berühmten "Geschichte der westlichen Philosophie" an, weil er heuchlerisch die Askese lobte, aber nicht danach handelte. Der holländische Mathematiker L. E. J. Brouwer, der auf der gegenüberliegenden Insel von Russell über die Grundlagen der Mathematik sprach, nahm die Ideen von Kant und Schopenhauer in den Intuitionismus auf, in dem die Mathematik als eine rein geistige Tätigkeit betrachtet wird und nicht als eine analytische Tätigkeit, bei der die objektiven Eigenschaften der Realität berücksichtigt werden.""",
meta={"name": "1"},
id="3",
),
Document(
content="""Das dothrakische Vokabular wurde von David J. Peterson lange vor der Verfilmung erstellt. HBO beauftragte das Language Creatio""",
meta={"name": "2"},
id="4",
),
Document(
content="""Der Titel der Episode bezieht sich auf die Große Septe von Baelor, das wichtigste religiöse Gebäude in Königsmund, in dem die Schlüsselszene der Episode stattfindet. In der von George R. R. Martin geschaffenen Welt""",
meta={},
id="5",
),
]
results = ranker_two_logits.predict(query=query, documents=docs)
assert results[0] == docs[4]