与 BERT 的字符串比较似乎忽略了句子中的 "not"
String comparison with BERT seems to ignore "not" in sentence
我使用 SentenceTransformers 和 BERT 实现了一个字符串比较方法,如下所示
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
model = SentenceTransformer('sentence-transformers/all-distilroberta-v1')
sentences = [
"I'm a good person",
"I'm not a good person"
]
sentence_embeddings = model.encode(sentences)
cosine_similarity(
[sentence_embeddings[0]],
sentence_embeddings[1:]
)
请注意我的句子示例非常相似但含义相反。问题是余弦相似度 returns 0.9,表明这两个字符串在上下文中非常相似,而我预期它 return 更接近于零,因为它们具有相反的含义。
如何使我的代码适应 return 更准确的结果?
结果并不意外。你通过了两个非常相似但含义相反的句子。句子嵌入是从在通用语料库上训练的模型获得的,因此,如果句子相似,则通常期望模型给出的嵌入彼此接近。这就是正在发生的事情,余弦相似性表明嵌入彼此接近,句子也是如此。例子中的句子可能意思相反,但大同小异
在这种情况下,如果你希望两个相似的句子彼此远离基于正面和负面情绪)。或其他一些相关任务。
TL;DR:NLI 就是您所需要的
首先,余弦相似度相当高,因为句子在以下意义上相似:
- 他们是同一个话题(对一个人的评价)
- 他们是同一个主题(“我”)和同一个 属性(“做一个好人”)
- 它们具有相似的句法结构
- 他们的词汇量几乎相同
所以,从形式上来说,应该算是相似吧。而且,从实用的角度来看,它们通常应该被认为是相似的。例如,如果您 google“GMO are causing cancer”,您可能会发现带有标签“GMO are not causing cancer”的文本是相关的。
其次,如果要衡量句子之间的逻辑联系,嵌入的余弦相似度表达力不够。这是因为嵌入包含大量语义风格、词汇和句法信息,但它们是固定大小的(在您的情况下为 768 维),因此它们不能包含有关两个句子含义的完整信息。所以你需要另一个具有以下属性的模型:
- 它同时对两个文本进行编码,因此它比较文本本身,而不仅仅是它们的固定大小嵌入
- 它经过明确训练可以评估句子之间的逻辑联系
评估文本之间的逻辑联系的任务称为自然语言推理 (NLI),其最常见的表述是识别文本蕴含(RTE):是预测第一句是否蕴含第二句的问题。
在 Huggingface 存储库中有很多针对此任务训练的模型,roberta-large-mnli 是一个很好的模型。您可以使用它来评估两个文本的等价性。如果每个文本蕴涵另一个文本,则它们是等价的,因此您可以将等价程度估计为两个方向的蕴涵分数的乘积。
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification
tokenizer = AutoTokenizer.from_pretrained("roberta-large-mnli")
model = AutoModelForSequenceClassification.from_pretrained("roberta-large-mnli")
def test_entailment(text1, text2):
batch = tokenizer(text1, text2, return_tensors='pt').to(model.device)
with torch.no_grad():
proba = torch.softmax(model(**batch).logits, -1)
return proba.cpu().numpy()[0, model.config.label2id['ENTAILMENT']]
def test_equivalence(text1, text2):
return test_entailment(text1, text2) * test_entailment(text2, text1)
print(test_equivalence("I'm a good person", "I'm not a good person")) # 2.0751484e-07
print(test_equivalence("I'm a good person", "You are a good person")) # 0.49342492
print(test_equivalence("I'm a good person", "I'm not a bad person")) # 0.94236994
我使用 SentenceTransformers 和 BERT 实现了一个字符串比较方法,如下所示
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
model = SentenceTransformer('sentence-transformers/all-distilroberta-v1')
sentences = [
"I'm a good person",
"I'm not a good person"
]
sentence_embeddings = model.encode(sentences)
cosine_similarity(
[sentence_embeddings[0]],
sentence_embeddings[1:]
)
请注意我的句子示例非常相似但含义相反。问题是余弦相似度 returns 0.9,表明这两个字符串在上下文中非常相似,而我预期它 return 更接近于零,因为它们具有相反的含义。
如何使我的代码适应 return 更准确的结果?
结果并不意外。你通过了两个非常相似但含义相反的句子。句子嵌入是从在通用语料库上训练的模型获得的,因此,如果句子相似,则通常期望模型给出的嵌入彼此接近。这就是正在发生的事情,余弦相似性表明嵌入彼此接近,句子也是如此。例子中的句子可能意思相反,但大同小异
在这种情况下,如果你希望两个相似的句子彼此远离基于正面和负面情绪)。或其他一些相关任务。
TL;DR:NLI 就是您所需要的
首先,余弦相似度相当高,因为句子在以下意义上相似:
- 他们是同一个话题(对一个人的评价)
- 他们是同一个主题(“我”)和同一个 属性(“做一个好人”)
- 它们具有相似的句法结构
- 他们的词汇量几乎相同
所以,从形式上来说,应该算是相似吧。而且,从实用的角度来看,它们通常应该被认为是相似的。例如,如果您 google“GMO are causing cancer”,您可能会发现带有标签“GMO are not causing cancer”的文本是相关的。
其次,如果要衡量句子之间的逻辑联系,嵌入的余弦相似度表达力不够。这是因为嵌入包含大量语义风格、词汇和句法信息,但它们是固定大小的(在您的情况下为 768 维),因此它们不能包含有关两个句子含义的完整信息。所以你需要另一个具有以下属性的模型:
- 它同时对两个文本进行编码,因此它比较文本本身,而不仅仅是它们的固定大小嵌入
- 它经过明确训练可以评估句子之间的逻辑联系
评估文本之间的逻辑联系的任务称为自然语言推理 (NLI),其最常见的表述是识别文本蕴含(RTE):是预测第一句是否蕴含第二句的问题。
在 Huggingface 存储库中有很多针对此任务训练的模型,roberta-large-mnli 是一个很好的模型。您可以使用它来评估两个文本的等价性。如果每个文本蕴涵另一个文本,则它们是等价的,因此您可以将等价程度估计为两个方向的蕴涵分数的乘积。
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification
tokenizer = AutoTokenizer.from_pretrained("roberta-large-mnli")
model = AutoModelForSequenceClassification.from_pretrained("roberta-large-mnli")
def test_entailment(text1, text2):
batch = tokenizer(text1, text2, return_tensors='pt').to(model.device)
with torch.no_grad():
proba = torch.softmax(model(**batch).logits, -1)
return proba.cpu().numpy()[0, model.config.label2id['ENTAILMENT']]
def test_equivalence(text1, text2):
return test_entailment(text1, text2) * test_entailment(text2, text1)
print(test_equivalence("I'm a good person", "I'm not a good person")) # 2.0751484e-07
print(test_equivalence("I'm a good person", "You are a good person")) # 0.49342492
print(test_equivalence("I'm a good person", "I'm not a bad person")) # 0.94236994