Hugging FaceのTokenizerを理解する|動作まとめ
この記事では、HuggingFaceのモデル(BERT等)を利用されるトークナイザー(Tokenizer)の動作について解説します。記事では、bertなどの代表モデルのトークナイザーを使って、「トークン化した出力はどのようなものなのか?」などについて実際に動作させて確認しました。この記事が、トークナイザーの動作の理解に助けになれば幸いです。
Tokenizerの動きがよくわからない
BERTなどのモデルを使用する際には、まずテキストをトークナイザーでトークン化してから入力します。
通常、モデルとトークナイザーは対応するペアで利用します。今回は、Tokenizerの具体的な動作がよくわからない部分があったため、いくつかのパラメータを試してその挙動を確認してみました。
ちなみに、特によく使うパラメータとしては、padding
、truncation
、max_length
の3つが挙げられます。これらのパラメータについて、実は自分でも挙動を勘違いしていた点があり、実際に確認しながら理解を深めることができました。
padding
,truncation
はなぜするのか
機械学習を行う場合は、入力を固定長にしたほうが都合が良いことが多いです。また、入力サイズが決まっている場合には、それより長い部分はカットする必要があります。なので、padding, truncationを行う必要があります。
Hugging Faceに登録されているtransformerの場合、この辺りをよしなにやってくれているやつもある気がします。ここは、また調査したいと思います。
動作確認の結果
トークナイザーを取得
今回は、基本となるbert-base-uncased
のモデルで実験を行いました。以下がtokenizerを取得するコードです。
from transformers import AutoTokenizer
model_ckpt = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_ckpt)
事前にpipでhugging faceをインストールしておいてください。
pip install transformers
実験に使ったテキスト
トークナイザーはリスト形式で複数のテキストを渡すと、それぞれをトークン化してくれます。今回は、ワード数の異なる3つのテキストを用意しました(とりあえず、天気関係で統一していたりします)。
texts = ["The sun shines brightly today.",
"The sun shines brightly on a clear summer morning.",
"Today is fine."]
トークン化した結果を表示する関数
結果表示にはこちらを使いました
def print_result(token) :
print("[input_ids]:")
for e in result['input_ids'] :
print(e)
print("[token_type_ids]:")
for e in result['token_type_ids'] :
print(e)
print("[attention_mask]:")
for e in result['attention_mask'] :
print(e)
引数を設定しない場合
まず、何も引数を指定しない場合の動きを確認します。
result = tokenizer(texts)
print_result(result)
出力の意味はおおよそ、以下の通りです
- input_idsは、テキストをトークン化したものになります
- token_type_idsは、特殊なタイプがなければ0です
- attention_maskは、パティングされた部分が0になります。
それぞれ、トークン化されていますが、長さはまちまちです。それぞれ、異なるサイズでトークン化されていることがわかります。
[input_ids]:
[101, 1996, 3103, 12342, 2015, 14224, 2651, 1012, 102]
[101, 1996, 3103, 12342, 2015, 14224, 2006, 1037, 3154, 2621, 2851, 1012, 102]
[101, 2651, 2003, 2986, 1012, 102]
[token_type_ids]:
[0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0]
[attention_mask]:
[1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1]
上記の結果を見てわかるように、トークン化では、文字列をID(番号)に変換する処理が行われます。BERTなどのモデルでは、この番号を埋め込み層で多次元ベクトルに変換します。
LLMのトークン化については、以下の記事がわかりやすいです。
トークンを逆変換するには、tokenizer.encode
関数を利用します。
以下は、texts[0]をトークン化した、input_ids
を逆変換する例です。
result = tokenizer(texts[0])
for e in result['input_ids']:
r = tokenizer.decode(e, skip_special_tokens=True)
print(e, r)
注意:トークン化された結果は、完全に元のテキストには戻りません
padding=True
を設定
padding=True
を設定した場合の挙動です。
result = tokenizer(texts, padding=True)
print_result(result)
padding=Trueにすると、入力したテキストのトークン数が一番多いものに合わせて0が挿入されていることがわかります。
[input_ids]:
[101, 1996, 3103, 12342, 2015, 14224, 2651, 1012, 102, 0, 0, 0, 0]
[101, 1996, 3103, 12342, 2015, 14224, 2006, 1037, 3154, 2621, 2851, 1012, 102]
[101, 2651, 2003, 2986, 1012, 102, 0, 0, 0, 0, 0, 0, 0]
[token_type_ids]:
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[attention_mask]:
[1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]
padding=True, max_length=20
を設定
padding=True, max_length=20
を設定した場合の動きを確認してみました
result = tokenizer(texts, padding=True, max_length=20)
print_result(result)
予想と異なり、padding=True
とした場合と同じです。予想ではmax_lengthで指定した20トークンになるようにパディングされると思っていましたが実際の動作は異なります。要注意です。
[input_ids]:
[101, 1996, 3103, 12342, 2015, 14224, 2651, 1012, 102, 0, 0, 0, 0]
[101, 1996, 3103, 12342, 2015, 14224, 2006, 1037, 3154, 2621, 2851, 1012, 102]
[101, 2651, 2003, 2986, 1012, 102, 0, 0, 0, 0, 0, 0, 0]
[token_type_ids]:
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[attention_mask]:
[1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]
padding="max_length", max_length=20
を設定
調べてみるとpadding
にはmax_lengthを指定できるようです
result = tokenizer(texts, padding="max_length", max_length=20)
print_result(result)
この設定を行うと、max_length以下の部分にパディングが行われるようになりました。max_lengthに合わせてパディングを行いたい場合には、この設定を行う必要があるようです。
[input_ids]:
[101, 1996, 3103, 12342, 2015, 14224, 2651, 1012, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[101, 1996, 3103, 12342, 2015, 14224, 2006, 1037, 3154, 2621, 2851, 1012, 102, 0, 0, 0, 0, 0, 0, 0]
[101, 2651, 2003, 2986, 1012, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[token_type_ids]:
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[attention_mask]:
[1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]
[1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
padding=True, truncation=True, max_length=7
を設定
truncation
の動きを確認するために、上記の設定を行ってみました。max_lengthを7に設定したのは、一番短いテキストが6トークンなので、少ない場合の挙動がどうなるかを確認するためです。
result = tokenizer(texts, padding=True, truncation=True, max_length=7)
print_result(result)
こちらの設定で、7文字以上の部分はカット、7文字以下の部分はパディングという所望通りの動作をしました。
[input_ids]:
[101, 1996, 3103, 12342, 2015, 14224, 102]
[101, 1996, 3103, 12342, 2015, 14224, 102]
[101, 2651, 2003, 2986, 1012, 102, 0]
[token_type_ids]:
[0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0]
[attention_mask]:
[1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 0]
padding=True, truncation=False, max_length=5
を設定
truncation=False
を設定するとどうなるでしょうか。
result = tokenizer(texts, padding=True, truncation=False, max_length=5)
print_result(result)
想定通り打ち切りがなくなりました。padding=True
にしているので、一番長いトークン数に合わせてパディングは行われています。
[input_ids]:
[101, 1996, 3103, 12342, 2015, 14224, 2651, 1012, 102, 0, 0, 0, 0]
[101, 1996, 3103, 12342, 2015, 14224, 2006, 1037, 3154, 2621, 2851, 1012, 102]
[101, 2651, 2003, 2986, 1012, 102, 0, 0, 0, 0, 0, 0, 0]
[token_type_ids]:
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[attention_mask]:
[1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]
まとめ
疑問を感じたのでトークナイザーの動きを確認してみました。
参考になれば幸いです