Views
No views yet
1from transformers import AutoTokenizer,AutoModel, PreTrainedModel,PretrainedConfig
2from typing import Dict
3import torch
4
5class ColBERTConfig(PretrainedConfig):
6 model_type = "ColBERT"
7 bert_model: str
8 compression_dim: int = 768
9 dropout: float = 0.0
10 return_vecs: bool = False
11 trainable: bool = True
12
13class ColBERT(PreTrainedModel):
14 """
15 ColBERT model from: https://arxiv.org/pdf/2004.12832.pdf
16 We use a dot-product instead of cosine per term (slightly better)
17 """
18 config_class = ColBERTConfig
19 base_model_prefix = "bert_model"
20
21 def __init__(self,
22 cfg) -> None:
23 super().__init__(cfg)
24
25 self.bert_model = AutoModel.from_pretrained(cfg.bert_model)
26
27 for p in self.bert_model.parameters():
28 p.requires_grad = cfg.trainable
29
30 self.compressor = torch.nn.Linear(self.bert_model.config.hidden_size, cfg.compression_dim)
31
32 def forward(self,
33 query: Dict[str, torch.LongTensor],
34 document: Dict[str, torch.LongTensor]):
35
36 query_vecs = self.forward_representation(query)
37 document_vecs = self.forward_representation(document)
38
39 score = self.forward_aggregation(query_vecs,document_vecs,query["attention_mask"],document["attention_mask"])
40 return score
41
42 def forward_representation(self,
43 tokens,
44 sequence_type=None) -> torch.Tensor:
45
46 vecs = self.bert_model(**tokens)[0] # assuming a distilbert model here
47 vecs = self.compressor(vecs)
48
49 # if encoding only, zero-out the mask values so we can compress storage
50 if sequence_type == "doc_encode" or sequence_type == "query_encode":
51 vecs = vecs * tokens["tokens"]["mask"].unsqueeze(-1)
52
53 return vecs
54
55 def forward_aggregation(self,query_vecs, document_vecs,query_mask,document_mask):
56
57 # create initial term-x-term scores (dot-product)
58 score = torch.bmm(query_vecs, document_vecs.transpose(2,1))
59
60 # mask out padding on the doc dimension (mask by -1000, because max should not select those, setting it to 0 might select them)
61 exp_mask = document_mask.bool().unsqueeze(1).expand(-1,score.shape[1],-1)
62 score[~exp_mask] = - 10000
63
64 # max pooling over document dimension
65 score = score.max(-1).values
66
67 # mask out paddding query values
68 score[~(query_mask.bool())] = 0
69
70 # sum over query values
71 score = score.sum(-1)
72
73 return score
74
75tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased") # honestly not sure if that is the best way to go, but it works :)
76model = ColBERT.from_pretrained("sebastian-hofstaetter/colbert-distilbert-margin_mse-T2-msmarco")| MRR@10 | NDCG@10 | |
|---|---|---|
| BM25 | .194 | .241 |
| Margin-MSE ColBERT (Re-ranking) | .375 | .436 |
| MRR@10 | NDCG@10 | |
|---|---|---|
| BM25 | .689 | .501 |
| Margin-MSE ColBERT (Re-ranking) | .878 | .744 |
@misc{hofstaetter2020_crossarchitecture_kd,
title={Improving Efficient Neural Ranking Models with Cross-Architecture Knowledge Distillation},
author={Sebastian Hofst{\"a}tter and Sophia Althammer and Michael Schr{\"o}der and Mete Sertkan and Allan Hanbury},
year={2020},
eprint={2010.02666},
archivePrefix={arXiv},
primaryClass={cs.IR}
}