如何从 BERT 模型中获取词嵌入的余弦相似度

How to get cosine similarity of word embedding from BERT model

我对如何从 BERT 模型中获得词嵌入在不同句子中的相似性很感兴趣(实际上,这意味着词在不同场景下具有不同的含义)。

例如:

sent1 = 'I like living in New York.'
sent2 = 'New York is a prosperous city.'

我想从 sent1 和 sent2 中获取 cos(New York, New York) 的值,即使短语 'New York' 相同,但出现在不同的句子中。我从 https://discuss.huggingface.co/t/generate-raw-word-embeddings-using-transformer-models-like-bert-for-downstream-process/2958/2

得到了一些直觉

但是我仍然不知道我需要提取哪一层的嵌入以及如何为我上面的例子计算cos相似度。

提前感谢您的任何建议!

好的,我们开始吧。

首先你需要了解BERT有13层。第一层基本上只是 BERT 在初始训练期间通过的嵌入层。您可以使用它,但可能不想使用它,因为它本质上是一个静态嵌入,而您在进行动态嵌入。为简单起见,我将只使用 BERT 的最后一个隐藏层。

您在这里使用了两个词:“New”和“York”。你可以在预处理过程中把它当作一个,如果你真的想要的话,可以把它组合成“纽约”或其他东西。在这种情况下,我将把它视为两个单独的词,并对 BERT 生成的嵌入进行平均。

这可以用几个步骤来描述:

  1. 标记输入
  2. 确定分词器在纽约和纽约的位置 word_ids(suuuuper 重要)
  3. 通过BERT
  4. 平均
  5. 余弦相似度

首先,您需要导入:from transformers import AutoTokenizer, AutoModel

现在我们可以创建分词器和模型了:

tokenizer = AutoTokenizer.from_pretrained('bert-base-cased')
model = model = AutoModel.from_pretrained('bert-base-cased', output_hidden_states=True).eval()

确保在评估模式下使用模型,除非您正在尝试微调!

接下来我们需要标记化(第 1 步):

tok1 = tokenizer(sent1, return_tensors='pt')
tok2 = tokenizer(sent2, return_tensors='pt')

第2步,需要判断词的索引在哪里匹配

# This is where the "New" and "York" can be found in sent1
sent1_idxs = [4, 5]
sent2_idxs = [0, 1]

tok1_ids = [np.where(np.array(tok1.word_ids()) == idx) for idx in sent1_idxs]
tok2_ids = [np.where(np.array(tok2.word_ids()) == idx) for idx in sent2_idxs]

以上代码检查标记器生成的 word_ids() 与原始句子中的单词索引重叠的位置。这是必要的,因为分词器会拆分稀有词。所以如果你有像“aardvark”这样的东西,当你标记它并查看它时,你实际上得到了这个:

In [90]: tokenizer.convert_ids_to_tokens( tokenizer('aardvark').input_ids)
Out[90]: ['[CLS]', 'a', '##ard', '##var', '##k', '[SEP]']

In [91]: tokenizer('aardvark').word_ids()
Out[91]: [None, 0, 0, 0, 0, None]

第3步.通过BERT

现在我们获取 BERT 在我们生成的令牌 ID 中生成的嵌入:

with torch.no_grad():
    out1 = model(**tok1)
    out2 = model(**tok2)

# Only grab the last hidden state
states1 = out1.hidden_states[-1].squeeze()
states2 = out2.hidden_states[-1].squeeze()

# Select the tokens that we're after corresponding to "New" and "York"
embs1 = states1[[tup[0][0] for tup in tok1_ids]]
embs2 = states2[[tup[0][0] for tup in tok2_ids]]

现在你将有两个嵌入。每个都是形状(2, 768)。第一个大小是因为您有两个我们正在查看的词:“New”和“York”。第二个大小是 BERT 的嵌入大小。

第 4 步。平均

好的,这不一定是您想要做的,但这取决于您如何处理这些嵌入。我们有两个 (2, 768) 形状的嵌入。您可以将纽约与纽约以及纽约与纽约进行比较,也可以将纽约合并为平均值。我会这样做,但如果另一个更适合您的任务,您可以轻松地做另一个。

avg1 = embs1.mean(axis=0)
avg2 = embs2.mean(axis=0)

步骤 5.余弦模拟

余弦相似度使用 torch:

很容易
torch.cosine_similarity(avg1.reshape(1,-1), avg2.reshape(1,-1))

# tensor([0.6440])

这个不错!他们指向同一个方向。它们不完全是 1,但可以通过多种方式进行改进。

  1. 您可以对训练集进行微调
  2. 您可以尝试平均不同的层,而不是像我那样只对最后一个隐藏层进行平均
  3. 你可以尝试把纽约和纽约结合起来发挥创意。我取了平均值,但也许有更好的方法可以满足您的确切需求。