HuggingFace BERT`inputs_embeds`给人意外的结果

时间:2020-05-02 23:18:17

标签: python tensorflow nlp huggingface-transformers bert-language-model

HuggingFace BERT TensorFlow implementation允许我们提供预计算的嵌入,以代替BERT固有的嵌入查找。使用模型的call方法的可选参数inputs_embeds(代替input_ids)可以完成此操作。为了测试这一点,我想确保,如果我 did 输入了BERT的嵌入查找,我得到的结果将与输入input_ids本身相同。

可以通过将BERT配置参数output_hidden_states设置为True并从call方法的最后一个输出中提取第一个张量来获得BERT嵌入查找的结果。 (其余的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给出的方程式的位置嵌入(但根据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

1 个答案:

答案 0 :(得分:1)

我关于添加位置和令牌类型嵌入的直觉是正确的。仔细查看code之后,我替换了以下行:

inputs_embeds = result[-1][0]

包含以下行:

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

现在,差异是0.0,如预期的那样。