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_states
为True
,从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,符合预期。
HuggingFace BERT TensorFlow implementation 允许我们输入预先计算的嵌入来代替 BERT 原生的嵌入查找。这是使用模型的 call
方法的可选参数 inputs_embeds
(代替 input_ids
)完成的。为了对此进行测试,我想确保如果我 did 输入 BERT 的嵌入查找,我会得到与输入 input_ids
本身相同的结果。
BERT的embedding lookup的结果可以通过设置BERT配置参数output_hidden_states
为True
,从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,符合预期。