HuggingFace BERT TensorFlow実装 を使用すると、BERTに固有の埋め込みルックアップの代わりに、事前計算された埋め込みをフィードできます。これは、モデルのcall
メソッドのオプションパラメータinputs_embeds
を使用して行われます(input_ids
の代わりに)。これをテストするために、BERTの埋め込みルックアップでdidをフィードした場合、input_ids
自体をフィードした場合と同じ結果が得られることを確認したいと思いました。
BERTの埋め込みルックアップの結果は、BERT構成パラメーターoutput_hidden_states
をTrue
に設定し、call
メソッドの最後の出力から最初のテンソルを抽出することで取得できます。 (残りの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で使用しています。
[〜#〜] edit [〜#〜]: HuggingFaceコード の一部を見ると、 input_ids
が指定されたときに検索される埋め込み、またはinputs_embeds
が指定されたときに割り当てられる埋め込みは、位置埋め込みとトークンタイプ埋め込みに追加されてから、後続のレイヤーにフィードされます。これが事実である場合、mayから得られるのは、result[-1][0]
が生の埋め込みと位置およびトークンタイプの埋め込みである可能性があります。これは、result[-1][0]
を計算するためにinputs_embeds
をresult2
としてフィードすると、誤って再度追加されることを意味します。
これが当てはまるかどうか教えていただけますか?そうである場合は、位置とトークンタイプの埋め込みを取得する方法を説明して、差し引くことができますか?以下は、与えられた方程式に基づいて位置埋め込みについて思いついたものです here (しかし BERT論文 によると、位置埋め込みは実際に学習される可能性があるため、 mこれらが有効かどうかは不明です):
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になります。