来自转换器的 BERT 句子嵌入
Posted
技术标签:
【中文标题】来自转换器的 BERT 句子嵌入【英文标题】:BERT sentence embeddings from transformers 【发布时间】:2020-12-07 04:54:04 【问题描述】:我正在尝试从 BERT 模型中的隐藏状态获取句子向量。看着拥抱脸的 BertModel 指令here,上面写着:
from transformers import BertTokenizer, BertModel
tokenizer = BertTokenizer.from_pretrained('bert-base-multilingual-cased')
model = BertModel.from_pretrained("bert-base-multilingual-cased")
text = "Replace me by any text you'd like."
encoded_input = tokenizer(text, return_tensors='pt')
output = model(**encoded_input)
所以首先要注意,就像在网站上一样,它不会/不/运行。你得到:
>>> Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'BertTokenizer' object is not callable
但它看起来像一个小的更改修复它,因为您不直接调用标记器,而是要求它对输入进行编码:
encoded_input = tokenizer.encode(text, return_tensors="pt")
output = model(encoded_input)
好吧,除此之外,我得到的张量的形状与我预期的不同:
>>> output[0].shape
torch.Size([1,11,768])
这是很多层。哪个是用于句子嵌入的正确层? [0]
? [-1]
?平均几个?我的目标是能够与这些进行余弦相似性,因此我需要一个适当的 1xN 向量而不是 NxK 张量。
我看到流行的bert-as-a-service project 似乎使用[0]
这是正确的吗?是否有关于每一层是什么的文档?
【问题讨论】:
关于TypeError: 'BertTokenizer' object is not callable
,您可能安装了旧版本的变压器。
【参考方案1】:
虽然Jindrich 的现有答案通常是正确的,但它并不能完全解决问题。 OP 询问他应该使用哪一层来计算句子嵌入之间的余弦相似度,这个问题的简短回答是 none。像余弦相似度这样的度量要求向量的维度贡献相等且有意义,但对于原作者发布的 BERT 权重而言,情况并非如此。雅各布·德夫林 (BERT论文的作者之一)wrote:
我不确定这些向量是什么,因为 BERT 不会生成有意义的句子向量。似乎这是对单词标记进行平均池化以获得句子向量,但我们从未建议这会生成有意义的句子表示。即使它们在输入为下游任务训练的 DNN 时是不错的表示,但这并不意味着它们在余弦距离方面有意义。 (因为余弦距离是一个线性空间,所有维度的权重均等)。
但是,这并不意味着您不能将 BERT 用于此类任务。这只是意味着您不能使用开箱即用的预训练权重。您可以在 BERT 之上训练一个分类器,该分类器学习哪些句子是相似的(使用 [CLS]
令牌),或者您可以使用 sentence-transformers,它可以在无监督场景中使用,因为它们经过训练可以产生有意义的句子表示。
【讨论】:
sentence-transformers 仍然仅限于句子,对吧?它不适用于没有 BERT 从单词到文档组合的那种失败的多句子文档? 不,您可以将它用于整个段落。 @Mittenchops 这是一个非常有趣的问题。所以,为了寻找相似的句子,你不会使用 BERT 嵌入的输出并尝试使用余弦相似度,对吗?但是如果这个想法不是寻找相似的句子而是寻找相似的词呢?我检索单词的嵌入并尝试在其他句子中寻找类似的嵌入。 @Borja_042 不,我不是在这里说的。我说过谷歌发布的原始 BERT 权重从未打算用于查找相似序列。您需要一些针对此任务训练的 BERT 权重。这就是句子转换器项目所做的。他们释放为这样一个目标而训练的权重。关于您的另一个问题,您是在寻找一种方法来确定一个单词在句子上下文中的相似性还是仅仅为了同义词? @cronoik 感谢您的回答。当您说您需要一些针对此任务训练的 BERT 权重时,您的意思是重新训练一个新的 Bert?还是使用已经从其他地方预训练的东西?我现在的任务是在纯文本上搜索实体,为此我正在从我想要查找的字段名称中进行嵌入,并且我也使用 Bert 将纯文本转换为向量。一旦我有了这两个向量,我就会检索到与我想要查找的字段最相似的词。我不知道 Bert 和这个方法是否是解决这个问题的有效方法。也许你可以指导我一点。非常感谢!【参考方案2】:我认为没有单一的权威文档说明使用什么以及何时使用。您需要试验和衡量最适合您的任务的方法。这篇论文很好地总结了最近对 BERT 的观察:https://arxiv.org/pdf/2002.12327.pdf。
我认为经验法则是:
如果您要针对特定任务微调模型,请使用最后一层。并且尽可能地微调,几百甚至几十个训练样例就足够了。
如果您无法微调模型,请使用一些中间层(第 7 层或第 8 层)。这背后的直觉是,这些层首先开发了一个越来越抽象和一般的输入表示。在某些时候,表示开始更多地针对预训练任务。
Bert-as-services 默认使用最后一层(但它是可配置的)。在这里,它将是[:, -1]
。但是,它总是返回所有输入标记的向量列表。第一个特殊(所谓的[CLS]
)标记对应的向量被认为是句子嵌入。这就是您所指的狙击手中[0]
的来源。
【讨论】:
聚合多个图层是否有意义,比如最后一个和倒数第二个?简单的算术平均值是否适合该操作? 确实如此。从某种意义上说,最后一层包含了之前的所有层,因为模型是通过残差连接互连的,即在每一层之后,该层的输出与前一层相加。由于存在残差连接,这些层在某种程度上是可通约的,平均它们只是改变了之前混合层的比率。 抱歉,图层的顺序是为了得到 /last/ 3 层,类似于:>>> output[0][:,-4:-1,:].shape.
For torch.Size([1, 3, 768])
对吗?
没错。 (顺便说一句。而不是-4-:1
,你只能写-4:
。)
很抱歉再次提出一个老问题,但是层子集肯定是输出[0]对象的中间维度?这似乎因文档长度而异。以上是关于来自转换器的 BERT 句子嵌入的主要内容,如果未能解决你的问题,请参考以下文章