HuggingFace BERT `inputs_embeds` 给出了意想不到的结果

HuggingFace BERT `inputs_embeds` giving unexpected result

HuggingFace BERT TensorFlow implementation 允许我们输入预先计算的嵌入来代替 BERT 原生的嵌入查找。这是使用模型的 call 方法的可选参数 inputs_embeds(代替 input_ids)完成的。为了对此进行测试,我想确保如果我 did 输入 BERT 的嵌入查找,我会得到与输入 input_ids 本身相同的结果。

BERT的embedding lookup的结果可以通过设置BERT配置参数output_hidden_statesTrue,从call方法的最后一个输出中提取第一个tensor得到。 (其余 12 个输出对应于 12 个隐藏层中的每一个。)

因此,我写了下面的代码来验证我的假设:

import tensorflow as tf
from transformers import BertConfig, BertTokenizer, TFBertModel

bert_tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')

input_ids = tf.constant(bert_tokenizer.encode("Hello, my dog is cute", add_special_tokens=True))[None, :]
attention_mask = tf.stack([tf.ones(shape=(len(sent),)) for sent in input_ids])
token_type_ids = tf.stack([tf.ones(shape=(len(sent),)) for sent in input_ids])

config = BertConfig.from_pretrained('bert-base-uncased', output_hidden_states=True)
bert_model = TFBertModel.from_pretrained('bert-base-uncased', config=config)

result = bert_model(inputs={'input_ids': input_ids, 
                            'attention_mask': attention_mask, 
                             'token_type_ids': token_type_ids})
inputs_embeds = result[-1][0]
result2 = bert_model(inputs={'inputs_embeds': inputs_embeds, 
                            'attention_mask': attention_mask, 
                             'token_type_ids': token_type_ids})

print(tf.reduce_sum(tf.abs(result[0] - result2[0])))  # 458.2522, should be 0

同样,call 方法的输出是一个元组。这个元组的第一个元素是最后一层 BERT 的输出。因此,我希望 result[0]result2[0] 匹配。 为什么不是这样?

我正在使用 Python 3.6.10,tensorflow 2.1.0 版和 transformers 2.5.1 版。

编辑:查看一些HuggingFace code,似乎在input_ids时查找的原始嵌入是在inputs_embeds 在被馈送到后续层之前被添加到位置嵌入和标记类型嵌入中。如果是这种情况,那么 可能 我从 result[-1][0] 得到的是原始嵌入加上位置和标记类型嵌入。这意味着当我将 result[-1][0] 作为 inputs_embeds 以计算 result2.

时,它们会被错误地再次添加

有人能告诉我是否是这种情况,如果是,请解释如何获得位置和令牌类型嵌入,以便我可以减去它们? 下面是我根据给定的方程 here (but according to the BERT paper 提出的位置嵌入,位置嵌入实际上可能是学习到的,所以我不确定这些是否有效):

import numpy as np

positional_embeddings = np.stack([np.zeros(shape=(len(sent),768)) for sent in input_ids])
for s in range(len(positional_embeddings)):
    for i in range(len(positional_embeddings[s])):
        for j in range(len(positional_embeddings[s][i])):
            if j % 2 == 0:
                positional_embeddings[s][i][j] = np.sin(i/np.power(10000., j/768.))
            else:
                positional_embeddings[s][i][j] = np.cos(i/np.power(10000., (j-1.)/768.))
positional_embeddings = tf.constant(positional_embeddings)
inputs_embeds += positional_embeddings

我对添加位置和令牌类型嵌入的直觉证明是正确的。在仔细查看 code 之后,我将行替换为:

inputs_embeds = result[-1][0]

行:

embeddings = bert_model.bert.get_input_embeddings().word_embeddings
inputs_embeds = tf.gather(embeddings, input_ids)

现在,差异为 0.0,符合预期。