我正在尝试从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]
这是正确的吗?是否有关于每个层是什么的文档?
答案 0 :(得分:5)
我认为没有单一权威文档说明使用什么以及何时使用。您需要进行实验并衡量最适合您任务的内容。本文很好地总结了有关BERT的最新观察结果:https://arxiv.org/pdf/2002.12327.pdf。
我认为经验法则是:
如果要针对特定任务微调模型,请使用最后一层。而且只要有可能就微调,数百个甚至几十个训练示例就足够了。
如果无法微调模型,请使用一些中间层(第7层或第8层)。这背后的直觉是,这些层首先开发了越来越抽象的输入表示形式。在某个时候,表示形式开始更倾向于预训练任务。
Bert-as-services默认使用最后一层(但它是可配置的)。此处为[:, -1]
。但是,它总是返回所有输入令牌的向量列表。对应于第一个特殊标记(所谓的[CLS]
)的向量被视为句子嵌入。 [0]
的位置来自您所引用的快照器。
答案 1 :(得分:2)
虽然Jindrich的现有答案通常是正确的,但并不能完全解决问题。 OP询问他应该使用哪一层来计算句子嵌入之间的余弦相似度,对此问题的简短回答是无。像余弦相似度这样的度量标准要求向量的维数相等且有意义地起作用,但是BERT并非如此。雅各布·德夫林 (BERT论文的作者之一)wrote:
我不确定这些向量是什么,因为BERT不会生成有意义的句子向量。似乎这是对单词标记进行平均池化以获得句子向量,但我们从未建议过这会产生有意义的句子表示形式。而且即使将它们作为接受了下游任务训练的DNN的代表,也并不意味着它们在余弦距离方面将是有意义的。 (由于余弦距离是一个线性空间,其中所有维度的权重均相等)。
但是,这并不意味着您不能将BERT用于此类任务。这仅意味着您不能开箱即用地使用预先训练的重量。您可以在BERT之上训练一个分类器来学习哪些句子相似(使用[CLS]
标记),也可以使用sentence-transformers,该分类器可以在无人监督的情况下使用,因为它们经过训练可以产生有意义的结果句子表示法。