ディープラーニングAI_人工知能TED AI Lab

大規模自然言語モデル(LLM)で利用されるトークナイザーについて

みなさん、こんにちは
CerebrasプリセールスエンジニアのNakadaです。
これまで弊社ブログにてCerebras及びLLM (大規模自然言語モデル)について投稿していましたが、今回は、LLMに重要なトークナイザーについて説明します。

■目次

 

■トークナイザーについて


そもそも、トークナイザーとは何?ですが、その前にLLMはどのように言語を学習しているかについて簡単に説明します。まず前提としてコンピュータは言語を文字としてそのまま理解することはできません。例えば、コンピュータに「東京」と表示した場合、コンピュータはASCIIコードと呼ばれる英数字のコードで文字を認識しています。「東京」の場合、「東=456C」「京=4EAC」となります。日本語には主に漢字、ひらがな、カタカナがありますが、1文字ごとにこのASCIIコードが割り当てられていて、コンピュータはASCIIコードの英数字で文字を認識します。

LLMではこのASCIIコードで言語を認識して学習しているのかというと、そうではありません。ここで登場するのが、トークナイザーです。トークナイザーは入力された文章を細かく分割する機能です。たとえば、「東京エレクトロンデバイスは、日本の会社です」という言葉を入力すると「’東京’, ‘エレクト’, ‘ロン’, ‘デバイス’, ‘は’, ‘、’, ‘日本の’, ‘会社’,です’」というように分割することが出来ます。

LLMではこの分割された単語に一意のID(数字)を割り当てて、この数字を使ってベクトル計算をし、文字の類似度をコンピュータで学習することができるようになります。

東京 ID:10
エレクト ID:240
ロン ID:50
デバイス ID:400
ID:30
ID:15
日本の ID:1000
会社 ID:2000
です ID:768

※注 IDの番号は説明のために割り当てた架空の番号です。

ちなみに、どのようなルールで分割するのか疑問に持つ方がいると思います。英語の場合は、単語間にスペースが入るため分割するのは容易ですが、日本語の場合はLLMよりも前からコンピュータで言語解析するために利用されている日本語を分割する方法があり、その仕組みとして形態素解析というものがあります。これは品詞を元に文章を分割しますが、mecab・sudachiなどの形態素解析ソフトウェアを実装することで実現することができます。

なお、Transformer系LLMでは形態素解析ではなく、BPE(Byte pair Encoder)という手法を用いたトークナイザーを利用することが多いです。BPEの方法としてはまず全て1文字で区切り、それぞれの1文字に対し、頻出する隣接文字があれば文字同士を結合するという方法です。これを繰り返して単語のようなもの(サブワード)を作ります。詳細については別の機会にお話できればと思います。

■トークナイザーを使ってみる


今回は、pythonとHugging FaceのTransformersライブラリを使って、トークナイザーを利用します。※事前にHugging FaceのTransformersライブラリをpipでインストールします。

まず初めにオープンソースとして公開されているLLaMA2のトークナイザーを使ってみます。このトークナイザーは先ほど触れたBPEがベースとなっているトークナイザーです。

※注 LLaMA2をダウンロードするためには、Meta社へ事前に利用登録する必要があります。また、HuggingfaceのTransformersライブラリで利用する場合はHuggingfaceのアカウントも登録し連携する必要があります。

【コード】
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(“meta-llama/Llama-2-7b-chat-hf”)
text = “東京エレクトロンデバイスは、日本の会社です”
encoded_text = tokenizer(text)
print(encoded_text)

Llama-2-7b-chat-hfで利用されているトークナイザーをAutoTokenizer.from_pretrainedでロードし、textに設定した文章をトークナイズ(分割)しました。

【print(encoded_text)の出力】
{‘input_ids’: [1, 29871, 30591, 30675, 30600, 30420, 30305, 30279, 30378, 30203, 30597, 30517, 30260, 30255, 30449, 30330, 30325, 30346, 30199, 30437, 30564, 30499, 30427]
‘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], ‘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]}

結果は【print(encoded_text)の出力】となりますが、’input_ids’が表示され、たくさんの番号が並んでいます。気づいた方もいると思いますが、これが、単語に割り当てられた一意のIDです。それでは、このIDを元に文字に変換します。

【コード】
for ids in encoded_text[“input_ids”]:print(tokenizer.decode(ids),end=’ , ‘)

【print(tokenizer.decode(ids),end=’ , ‘)の出力】
<s> , , 東 , 京 , エ , レ , ク , ト , ロ , ン , デ , バ , イ , ス , は , 、 , 日 , 本 , の , 会 , 社 , で , す , >>>

如何でしょうか。入力した文章が1文字ごとに分割されています。

それでは、別のトークナイザーとして、NovelAI社のnerdstash-tokenizer-v1を使ってみます。こちらもBPEベースのトークナイザーです。

【コード】
from transformers import LlamaTokenizer
tokenizer = LlamaTokenizer.from_pretrained(“NovelAI/nerdstash-tokenizer-v1”)
text = “東京エレクトロンデバイスは、日本の会社です”
encoded_text = tokenizer(text)
print(encoded_text)

【print(encoded_text)の出力】
{‘input_ids’: [2, 2206, 33103, 4508, 45444, 2808, 49235, 2290, 2886, 648], ‘attention_mask’: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}

こちらのトークナイザーのinput_idsの数が少ないことにお気付きでしょうか。文字に変換してみます。

【コード】
for ids in encoded_text[“input_ids”]:print(tokenizer.decode(ids),end=’ , ‘)

【print(tokenizer.decode(ids),end=’ , ‘)の出力】
<|startoftext|> , 東京 , エレクト , ロン , デバイ , スは , 、 , 日本の , 会社 , です , >>>

如何でしょうか。1文字ではなく複数の文字で1つの単語として分割されていることが分かります。実はこの違いが日本語に対応したトークナイザーということになります。最初に利用したLLaMA2のトークナイザーはLLaMA2学習済みモデルのトークナイザーを使いましたが、このモデルは英語対応モデルです。そのため、日本語は1文字ごとに分割されていたと考えられます。一方でNovelAI社のトークナイザーは日本語も対応したモデルのトークナイザーとなります。

■日本語トークナイザーを使うメリット


日本語に対応しているトークナイザーは分割する単位が複数文字で分割されることが分かりましたが、この違いにどのようなメリットがあるか説明します。その前に、LLaMA2のトークナイザーを使い、英文を分割させるとどのように分割されるのでしょうか。

【コード】
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(“meta-llama/Llama-2-7b-chat-hf”)
text = “Tokyo Electron Device is Japanese company”
encoded_text = tokenizer(text)
for ids in encoded_text[“input_ids”]:print(tokenizer.decode(ids),end=’ , ‘)

【print(tokenizer.decode(ids),end=’ , ‘)の出力】
<s> , Tokyo , Electron , Device , is , Japanese , company , >>>

結果は、英単語ごとに分割されていることが分かります。このメリットは、OpenAIのChatGPTを利用されている方はお気付きだと思います。

ChatGPTではGPT-4 TurboなどのLLMモデルを利用する場合、従量課金制になっていて、1M”トークン”当たり、Input $10/Output $30になります。(GPT-4 Turboの場合、2024/3現在)

このトークンとは、上記のトークナイザーで分割された単語1つが1トークンになります。

今回、「東京エレクトロンデバイスは、日本の会社です」という文章を異なるトークナイザーで分割しましたが、LLaMA2トークナイザーとNovelAIトークナイザーで分割後のトークン数が異なります。

LLaMA2トークナイザー 23トークン (スタートを表す特殊トークン<s>を含む)
NovelAIトークナイザー 10トークン (スタートを表す特殊トークン<|startoftext|>を含む)

同じ文章ですが、トークン数は日本語対応のトークナイザーの方が1/2のトークンで済むことなります。

このように、言語に対応したトークナイザーを利用することでトークン数を抑えることができ、LLMの学習・推論(文章生成時)の両方でパフォーマンス向上またはコスト削減などの恩恵を受けることができます。

注:既存のLLMモデルのトークナイザーを日本語に変更するだけで良いのか?という疑問がありますが、これはNOです。既存のモデルは利用していたトークナイザーで分割された単語を元に学習しているため、トークナイザーを変更するとファインチューニングする必要があります。この辺りの説明は次回以降でお話させていただく予定です。

■最後に


今回は、LLMのトークナイザーについて簡単に説明させていただきました。このトークナイザーは自身のデータセットを使って、学習することも可能です。この学習により、分割した単語(BPEの場合はサブワード)とIDを紐付ける語彙ファイル(vocab file)の生成も行われます。トークナイザーの学習はHugging FaceのTransformersライブラリを使うことで、比較的簡単に利用することができます。ただ、作成したトークナイザーの性能が果たして良いものなのか不安になる方もいるかもしれません。次回以降のブログでは、Hugging FaceのTransformersライブラリを使ったトークナイザーの学習と完成したトークナイザーの性能指標なども含めて、ご説明させていただきたいと思います。

最後に、ブログをお読みになり、LLM構築及びAIアクセラレータ製品等にご興味がある方は当社までお問合せ頂ければ幸いです。

この記事に関連する製品・サービス

この記事に関連する記事