機械学習
記事内に商品プロモーションを含む場合があります

Hugging FaceのTokenizerを理解する|動作まとめ

Aru

この記事では、HuggingFaceのモデル(BERT等)を利用されるトークナイザー(Tokenizer)の動作について解説します。記事では、bertなどの代表モデルのトークナイザーを使って、「トークン化した出力はどのようなものなのか?」などについて実際に動作させて確認しました。この記事が、トークナイザーの動作の理解に助けになれば幸いです。

Tokenizerの動きがよくわからない

BERTなどのモデルを使用する際には、まずテキストをトークナイザーでトークン化してから入力します。

通常、モデルとトークナイザーは対応するペアで利用します。今回は、Tokenizerの具体的な動作がよくわからない部分があったため、いくつかのパラメータを試してその挙動を確認してみました。

ちなみに、特によく使うパラメータとしては、paddingtruncationmax_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のトークン化については、以下の記事がわかりやすいです。

LLM(大規模言語モデル)の仕組みを分かりやすく解説|推論処理を理解しよう
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]

まとめ

疑問を感じたのでトークナイザーの動きを確認してみました。

参考になれば幸いです

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

ABOUT ME
ある/Aru
ある/Aru
IT&機械学習エンジニア/ファイナンシャルプランナー(CFP®)
専門分野は並列処理・画像処理・機械学習・ディープラーニング。プログラミング言語はC, C++, Go, Pythonを中心として色々利用。現在は、Kaggle, 競プロなどをしながら悠々自適に活動中
記事URLをコピーしました