このシリーズでは、自然言語処理において主流であるTransformerを中心に、環境構築から学習の方法までまとめます。
今回の記事ではHuggingface Transformersの入門として、トークナイザーの概要と基本的な扱い方を紹介します。
Google colabを使用して、簡単に最新の自然言語処理モデルを実装することができますので、ぜひ最後までご覧ください。
【前回】
今回の内容
・Tokenizerとは
・トークン化とは
・WordPieceによるトークン化
・日本語のTokenizer
・トークナイザーとモデル
Transformerとは
Transformerの概要
「Transformer」は2017年にGoogleが「Attention is all you need」で発表した深層学習モデルです。
現在では、自然言語処理に利用する深層学習モデルの主流になっています。
これまでの自然言語処理分野で多く使われていた「RNN」(Recurrent Neural Network)や「CNN」(Convolutional Neural Network)を利用せず、Attentionのみを用いたEncoder-Decoder型のモデルとなっています。
「Transformer」が登場して以降、多くの自然言語処理モデルが再構築され、過去を上回る成果を上げています。
最近では自然言語処理だけでなく、ViTやDETRなどといった画像認識にも応用されています。
これまでに登場した主なモデルを以下に示します。
登場年 | 主なモデル |
---|---|
2017年 | ・「Attention is all you need」というタイトルの論文で、初めてTransformerモデルの提案がなされた。この論文では、翻訳タスクを行うものであった。 |
2018年 | ・学習済みTransformerモデルとしてGPTが初めて登場。ファインチューニングによる様々なNLPタスクへの適用がなされた。 ・BERTが発表される。 |
2019年 | ・GPTの改良版として、よりサイズも大きいGPT2が誕生した。 ・BERTの蒸留モデルであるDistilBERTが誕生。BERTの97%の精度を維持しつつも、推論速度が60%速く、モデルのサイズも40%小さい。 ・BARTとT5という大規模データセットで学習済みのsequence-to-sequenceモデルが誕生。 |
2020年 | ・GPT-2よりも更に大きいGPT-3が誕生。Few-shot learningによりfine-tuningのコストを出来るだけ抑えて、様々なタスクに転用できるモデル。 ・Transformerを画像認識に応用したVision Transformerが誕生。画像分類タスクにおいて、CNN(畳み込み)を用いずに最高性能の記録。 |
2021年 | ・GPT3と同等の性能でありながら、より小型なモデルであるGPT-NEOやGPT-J-6Bが誕生。 |
2017年の登場以降、多くの派生モデルが登場していることがわかります。
Huggingface Transformersとは
「Hugging Face」とは米国のHugging Face社が提供している、自然言語処理に特化したディープラーニングのフレームワークです。
「Huggingface Transformers」は、先ほど紹介したTransformerを実装するためのフレームワークであり、「自然言語理解」と「自然言語生成」の最先端の汎用アーキテクチャ(BERT、GPT-2など)と、何千もの事前学習済みモデルを提供しています。
ソースコードは全てGitHub上で公開されており、誰でも無料で使うことができます。
Tokenizerとは
Transformerのモデルは、生のテキストデータのまま直接入力することはできないため、文字列をモデル使用される最小単位に分解する「トークン化」と、数値ベクトルとしてエンコードする必要があります。
これらの処理をまとめて行うのがTokenizerです。
Tokenizerを使用することで、以下のような処理を簡単にまとめて行うことができます。
① | テキストをトークンと呼ばれる最小の単位に区切る |
② | ぞれぞれのトークンにIDを振る |
③ | モデルの入力に必要な情報となるスペシャルトークンを入力テキストに追加する |
Tokenizerは、使用する学習済みモデルごとに作成されたものが存在するため、モデル学習時に使用されたTokenizerと同じものを使う必要があります。
AutoTokenizerクラスのfrom_pretrainedメソッドを使用することで、指定したモデルのTokenizerを使うことができます。
トークン化とは
Tokenizerを使用する前に、ここではトークン化について紹介します。
文字列をモデル使用される最小単位に分解する「トークン化」にはいくつか方法があります。
ここから英語のトークン化を紹介します。
文字トークン化と単語トークン化
英語をトークン化する上で最も簡単なトークン化として、文字トークン化が考えられます。
以下に文字レベルのトークン化の例を示します。
Hello world!
# 文字トークン化
'H','e','l','l','o','w','o','r','l','d','!'
この場合、辞書に含まれるトークンの種類(語彙数)が少なくなるというメリットがあります。
しかし、文字単位で区切ると情報が失われすぎてしまうことと、トークン数が多くなるため膨大な計算量とメモリが必要となるというデメリットがあります。
次に単語トークン化の例を示します。
Hello world!
# 文字トークン化
'Hello','world!'
単語トークン化はテキストを単語に分割し、各単語を整数に対応させます。
この場合、句読点や記号は考慮されていないため、扱いが複雑になります。
単語には語形変化、活用系、スペルミスなどを考慮すると数百万以上に膨れ上がるため、この場合でも膨大な計算量とメモリが必要となります。
サブワードトークン化
サブワードトークン化では、文字トークン化と単語トークン化の良いところを組み合わせています。
頻出単語については一意なものとして管理する一方で、稀な単語をより小さい単語に分割し、複雑な単語やスペルミスを処理できるようにしています。
サブワードトークン化の特徴は、統計的なルールとアルゴリズムを組み合わせて、事前学習用コーパスからトークン化を学習していることにあります。
WordPieceによるトークン化
WordPieceによるトークン化
サブワードトークン化のアルゴリズムの例として、BERTのトークナイザーで使われいている「WordPiece」があります。
ここからは、DistilBERTのトークナイザーの使用例を紹介します。
from transformers import AutoTokenizer
model_ckpt = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_ckpt)
次の例文に対して、トークナイザーを使用します。
text = "The brown cat jumps overt the white dog."
encoded_text = tokenizer(text)
print(encoded_text)
実行すると、以下のような結果が出力されます。
{'input_ids': [101, 1996, 2829, 4937, 14523, 2058, 2102, 1996, 2317, 3899, 1012, 102], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}
それぞれの単語に対して、テキストからIDへ変換することができました。
convert_ids_to_tokensメソッドを使用することで、IDからトークンに戻すこともできます。
tokens = tokenizer.convert_ids_to_tokens(encoded_text.input_ids)
print(tokens)
実行すると、以下のような結果が出力されます。
['[CLS]', 'the', 'brown', 'cat', 'jumps', 'over', '##t', 'the', 'white', 'dog', '.', '[SEP]']
[CLS]というBERTモデルで文頭を示すトークンであり、[SEP]という文の区切りを表すトークンを指しています。
convert_tokens_to_stringメソッドを使用することで、IDからテキストへの変換することができます。
print(tokenizer.convert_tokens_to_string(tokens))
実行すると、以下のような結果が出力されます。
[CLS] the brown cat jumps overt the white dog. [SEP]
日本語のTokenizer
ここからは日本語に対するTokenizerの実装例を紹介します。
まずは必要なライブラリをインストールします。
!pip install transformers[ja]
!pip install sentencepiece
以下の例では、日本語のBERTトークナイザーで使われいている「bert-base-japanese-whole-word-masking」があります。
ところで、Transformerモデルの入力としては、テンソル形式に変換する必要があります。
Tokenizerでは返り値のテンソルのタイプを選ぶことができます。
「return_tensors=’pt’」とすることで、PyTorchのテンソル型で返すことができます。
from transformers import AutoTokenizer
# 日本語BERTのTokenizerを読み込む
tokenizer_jp = AutoTokenizer.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking')
# サンプルデータ
raw_inputs = [
"私は毎週水曜日にカフェで勉強します。",
"その後、ジムに寄ってから帰ります。",
]
# サンプルデータを入力してTokenizerによる変換を行う
tokenized_inputs = tokenizer_jp(raw_inputs, padding=True, truncation=True, return_tensors='pt')
print(tokenized_inputs)
実行すると、以下の通り出力されます。
{'input_ids': tensor(
[[ 2, 1325, 9, 4255, 14475, 7, 8439, 12, 8192, 15,
2610, 8, 3],
[ 2, 366, 6, 5947, 7, 1397, 28468, 16, 40, 10766,
2610, 8, 3]]),
'token_type_ids': tensor(
[[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': tensor(
[[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]])}
返り値の各項目は以下の通りです。
input_ids | 入力テキストをトークンに分け、それぞれをIDに置き換えたもの。 |
attention_mask | 入力トークンについて、attentionの対象となるかを示すもの。 |
token_type_ids | 一つの入力が複数文になっている場合などに、各トークンについて、それぞれがどの文のものかを示す。 |
Tokenizerで使われているトークンとID
tokenizerで使われているトークンとIDを表示することができます。
print(tokenizer.get_vocab())
実際に表示してみると、以下のようになります。
※一部を抜粋したものです。
{'[PAD]': 0, '[UNK]': 1, '[CLS]': 2, '[SEP]': 3, '[MASK]': 4, 'の': 5, '、': 6, 'に': 7, '。': 8, 'は': 9, 'た': 10,
'##ーナメント': 4901, '三重': 4902, '手紙': 4903, '描写': 4904, '##ID': 4905, '智': 4906, '棟': 4907, '酵': 4908,
'出張': 9991, '出雲': 9992, '利点': 9993, '助言': 9994, '天下': 9995, '実態': 9996, '年末': 9997, '整数': 9998, 'ミラー': 9999,
トークンとIDの対応は辞書形式で取得できるようになっていることがわかります。
最初の方にはスペシャルトークンがあって、残りが単語や記号などのトークンという形になっています。
##の部分はワイルドカードになっており、”##ーナメント”は「”ーナメント”」で終わる単語というトークンを表していることになります。
IDからトークンへの変換
tokenizer.convert_ids_to_tokens()を使うことで、IDからトークンへ変換することができます。
converted_tokenized_inputs = [*map(lambda x: tokenizer.convert_ids_to_tokens(x), tokenized_inputs.input_ids)]
for inputs in converted_tokenized_inputs:
print(''.join(inputs))
以下のような結果が出力されます。
[CLS]私は毎週水曜日にカフェで勉強します。[SEP]
[CLS]その後、ジムに寄##ってから帰ります。[SEP]
tokenizerによって追加された[CLS]、[SEP]、[PAD]といったスペシャルトークンが含まれているのが確認できます。
なお、2文目では”寄”と”##って”という分け方になっているのが分かります。
tokenizer.decode()により、ワイルドカード部分も含めて元に戻すことができます。
for input_ids in tokenized_inputs.input_ids:
print(tokenizer.decode(input_ids).replace(' ', ''))
以下のような結果が出力されます。
[CLS]私は毎週水曜日にカフェで勉強します。[SEP]
[CLS]その後、ジムに寄ってから帰ります。[SEP]
トークナイザーとモデル
前回の記事と今回の記事では、トークナイザーとモデルの使い方を紹介してきました。
最後にこれらを組み合わせた簡単な使用例を紹介します。
まずは必要なライブラリをインストールします。
!pip install transformers[ja]
!pip install sentencepiece
ここでは日本語のBERTのモデルを使用します。
from transformers import BertJapaneseTokenizer, AutoModelForMaskedLM
tokenizer = BertJapaneseTokenizer.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking')
model = AutoModelForMaskedLM.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking')
Masked Language Modelにより、「tokenizer.mask_token」の単語を予測してみます。
import torch
# マスク付きテキスト
text = f'自然言語処理を習得するには、まずは{tokenizer.mask_token}から学習することである。'
# テキストをテンソルに変換
input_ids = tokenizer.encode(text, return_tensors='pt')
# マスクのインデックスを取得
masked_index = torch.where(input_ids == tokenizer.mask_token_id)[1].tolist()[0]
# 推論
result = model(input_ids)
pred_ids = result[0][:, masked_index].topk(5).indices.tolist()[0]
for pred_id in pred_ids:
output_ids = input_ids.tolist()[0]
output_ids[masked_index] = pred_id
print(tokenizer.decode(output_ids))
実行すると、以下のような結果が出力されました。
[CLS] 自然 言語 処理 を 習得 する に は 、 まずは 基礎 から 学習 する こと で ある 。 [SEP]
[CLS] 自然 言語 処理 を 習得 する に は 、 まずは そこ から 学習 する こと で ある 。 [SEP]
[CLS] 自然 言語 処理 を 習得 する に は 、 まずは 言語 から 学習 する こと で ある 。 [SEP]
[CLS] 自然 言語 処理 を 習得 する に は 、 まずは コンピュータ から 学習 する こと で ある 。 [SEP]
[CLS] 自然 言語 処理 を 習得 する に は 、 まずは 最初 から 学習 する こと で ある 。 [SEP]
まとめ
最後までご覧いただきありがとうございました。
今回の記事ではHuggingface Transformersの入門として、トークナイザーの基本的な扱い方を紹介しました。
次回はデータセットについて紹介します。
このシリーズでは、自然言語処理全般に関するより詳細な実装や学習の方法を紹介しておりますので、是非ご覧ください。
【次回】