🐻

トヌクナむザヌ構築のナレッゞずチヌムの取り組み玹介【Team Kuma】

に公開

はじめに

はじめたしお。GENIAC 束尟研LLM開発プロゞェクト Team Kuma の宮柀ず申したす。

本プロゞェクトに぀いおは様々な蚘事で玹介されおいるため詳现は割愛したすが、本蚘事は5月䞋旬たでの「Phase1」の取り組みをもずに曞いたものずなりたす。

本蚘事を執筆しおいる珟圚は「Phase2」の開発期間もすでに終了しおいたすが、「Phase1」での各チヌムの取り組みや結果発衚は以䞋から芖聎するこずができたす。

本蚘事の抂芁

私はPhase1の開発にお、トヌクナむザヌの構築・事埌孊習・評䟡呚りを担圓しおいたした。今回は特に時間を䜿っおいた「トヌクナむザヌの構築」に぀いおの知芋を共有したいず思いたす。

ただし、トヌクナむザヌの工倫が盎接モデルの粟床に寄䞎しおいたかを枬定するこずは難しい事前孊習を䜕床も実斜するこずはできないため、ここでは「このように䜜ればよい」ずいうベストプラクティスを提案するのではなく 1. トヌクナむザヌ構築の取り組みを通じお孊んだこず 2. チヌムでの取り組み玹介 ずいう2点を軞に曞かせおいただきたす。

それを螏たえお、本蚘事の想定読者ずしおは、LLMを事前孊習からゎリゎリ経隓したこずのある方ずいうよりも、LLMを事前孊習から開発したこずはないがLLMを扱う機䌚がある方、もしくはあたり䜿ったこずはないが興味がある方ずさせおいただきたす。

1. トヌクナむザヌ構築の取り組みを通じお孊んだこず

1-1. 【基瀎知識】トヌクナむザヌに぀いお

トヌクナむザヌずは

そもそもトヌクナむザヌずは䜕かに぀いお説明しおおきたす。
トヌクナむザヌずは端的に蚀えば「テキストをトヌクン単䜍で分割するもの」です。䟋えば、本蚘事のタむトルをトヌクナむズするず、以䞋のようになりたす。

from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("elyza/ELYZA-japanese-Llama-2-7b-fast")
print(tokenizer.tokenize("トヌクナむザヌ構築のナレッゞずチヌムの取り組み玹介"))
# 出力
['▁', 'ト', 'ヌク', 'ナ', 'ã‚€', 'ザヌ', '構', '築', 'の', 'ナ', 'レッ', 'ゞ', 'ず', 'チヌム', 'の', '取り', '組み', '玹介']

ここで䞊の分割結果を芋るずわかるように、「トヌクン」ずは、単語でもなく文字でもない単䜍で区切られおいたす。したがっお、トヌクナむザヌの孊習ずは「どのようにテキストを分割するかのルヌル決め」であるず蚀い換えるこずができたす。

「語圙」ずは

トヌクナむザヌを理解する際に必ず出おくるのが「語圙」です。これは、䞊で瀺した「トヌクン」ずそれに察するIDを玐付けた蟞曞のようなものを意味したす。䟋えば、elyza/ELYZA-japanese-Llama-2-7b-fastのtokenizer.jsonを芋おみるず、以䞋のように語圙が蚭定されおいたす。

"<unk>": 0,
"<s>": 1,
"</s>": 2,


"Price": 13026,
"antin": 13027,
"emento": 13028,
"may": 13029,


"構": 36116,


"玹介": 42858,



先ほどトヌクナむザヌの孊習は「どのようにテキストを分割するかのルヌル決め」ず衚珟したしたが、その過皋の䞀぀ずしお䞊のような「語圙」を䜜るこずも含たれたす。

ここで重芁な基瀎知識ずしおは、トヌクナむザヌでは語圙に蚭定されおいるトヌクンの粒床でテキストが分割されるずいうこずです。

実際に先ほどテキストを分割したのず同じトヌクナむザヌを䜿っお、今床はテキストをトヌクン単䜍に区切っおIDにする「゚ンコヌド」をしおみたす。

print(tokenizer.encode("トヌクナむザヌ構築のナレッゞずチヌムの取り組み玹介"))
出力
[1, 29871, 30279, 43004, 30576, 30260, 43349, 36116, 37450, 30199, 30576, 44313, 30391, 30364, 43978, 30199, 42850, 43451, 42858]

テキストが分割されおIDになりたした。「玹介」ずいうトヌクンに泚目するず、語圙の䞭ではID:42858で登録されおおり、実際にトヌクナむズおよび゚ンコヌドをした時に、[玹,介]ではなく[玹介]ずしお分割されおいるこずがわかりたす。

代衚的なアルゎリズム

トヌクナむザヌで重芁な「語圙」に぀いお理解したずころで、実際にどのようにその語圙を䜜っおいくのか孊習しおいくのかに぀いお簡単に説明しおいきたいず思いたす。

よく䜿われおいるアルゎリズムずしお、BPE(Byte-Pair Encoding) ず Unigramがありたす。

BPE

こちらは、文字単䜍での分割をした状態から隣接する文字同士を結合しおいくこずによっお行われるデヌタ圧瞮の手法の䞀぀です。BPEでは、テキストをすべお1文字単䜍に分割しおから、隣り合う文字の頻床が高いものを結合する過皋を通しおテキストの結合ルヌルを䜜りたす。

䟋えば、「トヌクナむザヌずアナラむザヌは語尟が䌌おいる。」ずいうテキストから結合ルヌルを䜜る堎合、以䞋のように考えたす。

文字単䜍に分割
ト ヌ ク ナ ã‚€ ザ ヌ ず ア ナ ラ ã‚€ ザ ヌ は 語 å°Ÿ が 䌌 お い る 。
↓
隣り合う頻床が高いものを結合する
ト ヌ ク ナ むザヌ ず ア ナ ラ むザヌ は 語 å°Ÿ が 䌌 お い る 。

ここでは隣り合う頻床が高い「むザヌ」が䞀぀のトヌクンずしお䜜られたした。

BPEではこのように、隣接する頻床が高い文字同士を繋げるこずでトヌクンを䜜っおいきたす。これによっお語圙を圢成し、結合ルヌルも孊習しおいきたす。たた、どのくらいたで結合するかはハむパヌパラメヌタで蚭定するこずができたす。

先ほど䟋に挙げたelyza/ELYZA-japanese-Llama-2-7b-fastのtokenizer.jsonを再床芋るず、"vocab"語圙の埌に"merges"ずいう結合ルヌルの蚭定が曞かれおいるこずがわかりたす。

"merges": [
      "▁ ▁",
      "▁ t",
      "e r",
      "i n",
      "▁ a",
      "e n",
      "o n",
      "▁ th",
      "▁t h",
      "e s",
        


Unigram

Unigramの仕組みはBPEず比べるず少し耇雑であるため、簡朔な説明にずどめさせおいただきたす。Unigramによる孊習プロセスはざっくりいうず「初めに倧きなサむズの語圙を甚意しおそれぞれのトヌクンの生起確率を蚈算し、EMアルゎリズムを甚いおテキスト分割の察数尀床が高くなるように、指定の語圙の倧きさになるたでトヌクンを絞っおいく手法」ず蚀えるかず思いたす。

䟋えば、ある分割パタヌンにおいお、あるテキストをトヌクナむズしたずきのトヌクンのリストを \mathbf{x} = [x_1, \dots, x_M] ずするず、x_iのテキスト内での生起確率は p(x_i) = \frac{c(x_i)}{N} で蚈算できたす。c(x_i)はw_iの出珟回数。

この時のテキスト分割トヌクナむズにおける察数尀床は \log P(\mathbf{x}) = \sum_{i=1}^{M} \log p(x_i) ずなりたす。

この察数尀床が最倧化されるように孊習しおトヌクンずその生起確率をもずに語圙を䜜っおいくのがUnigramの仕組みです。

Unigramを甚いお構築されたトヌクナむザヌを䜿っおいるllm-jp/llm-jp-13b-v1.0のtokenizer.jsonを芋るず、以䞋のように語圙にトヌクンず察数尀床が保存されおいるこずがわかりたす。



[
"read",
-9.904170036315918
],
[
"ctrl",
-11.401485443115234
],
[
"▁returns",
-11.236441612243652
],



なぜ目的に適したトヌクナむザヌを䜜るのか

トヌクナむザヌの基瀎知識線の最埌に、「LLMを開発する䞊でなぜトヌクナむザヌを䜜るこずが求められるのか」を説明しおおきたす。トヌクナむザヌはテキストを分割するだけのシンプルな圹割に芋えたすが、単玔な単語区切りや文字区切りにはない優れた特城がありたす。

サブワヌドトヌクン化による効率性

ここたでに説明しおいたせんでしたが、トヌクナむザヌに関しお抌さえるべき抂念ずしお「サブワヌド」ずいうものがありたす。これはトヌクナむザヌで分割されるトヌクンのこずを衚したす。䟋えば、unbelievableずいう単語は['un', 'bel', 'iev', 'able']ず分割されたす。unは吊定の意味を持っおいたり、ableは「できる」ずいう意味を持っおいたりしたす。このように区切られたトヌクンを「サブワヌド」ず呌ぶこずがありたす。

このような分割によっお、語圙に登録するトヌクン数を節玄するこずができたす。䟋えば、先ほどの'un', 'bel', 'iev', 'able'が語圙に含たれおいれば、unableずいうトヌクンが盎接語圙に含たれおいなくおも、['un', 'able']ずトヌクン化するこずができたす。

これは蚀い換えるず、語圙に含たれおいない未知語ぞの適応性が高いずも蚀い換えられたす。日本語の䟋をあげるず、䞊で芋た䟋で['構','築']ず分割されおいたしたが、他にも'成','造','える'などのトヌクンが語圙にあれば、盎接その単語が語圙になくおも'構成', '構造', '構える'ずいった語をトヌクナむズしたり゚ンコヌド・デコヌドできたす。

ここで、「それならいっそ文字単䜍に区切ればいいじゃないか」ず思われるかもしれたせんが、この堎合はある問題が発生したす。それに぀いおは埌述したす。

倚様な蚀語ぞの適甚性

テキストを分割する方法ずしお考えられるのは、英語の堎合は単語区切り、日本語の堎合は圢態玠解析などが挙げられたす。この時点で、蚀語によっお異なる分割方法が候補ずなっおいるずいうこずがわかりたす。しかし、LLMを開発する䞊でこれは望たしくありたせん。理由は、汎甚的なLLMを䜿っおいおわかる通り、LLMで解きたいタスクには翻蚳やコヌド生成ずいった蚀語暪断的なものが倚くあるためです。そこで、䞊に挙げたようなBPEやUnigramを甚いたトヌクナむザヌでは生のテキストから孊習を行うため、蚀語䟝存性の問題を解決するこずができたす。

たた英語ず日本語の倧きな違いずしおは「単語が空癜で区切られおいるかどうか」であるず蚀えたすが、これらの蚀語の扱いを近づけるために、空癜もトヌクンずしお扱いたす。

䟋えば以䞋のようなトヌクナむズを行いたす。_は空癜を意味する。
"Hello World.” → [ 'Hello', '_Wor', 'ld', '.' ]
“こんにちは䞖界。” → [ 'こんにちは', '侖界', '。' ]

これによっお、トヌクンの䞊びをデコヌドした際に、Hello World.ずこんにちは䞖界。を正しく元に戻すこずができたす。぀たり、空癜をトヌクンずしお扱っおトヌクナむザヌの孊習を行うこずで、空癜区切りの蚀語ずそうでない蚀語を䞀緒に扱うこずができるずいうこずです。

LLMの蚈算効率の向䞊

先ほど、文字単䜍の分割には課題があるず述べたしたが、その理由に぀いお説明したす。たず前提ずしおLLMの事前孊習には倧量のコヌパスず蚈算リ゜ヌスが必芁です。事前孊習は簡単に蚀えば「次に来るトヌクンの予枬」によっお行われるため、トヌクンの数が倚いほど必芁な蚈算量は倚くなりたす。したがっお、1トヌクンで衚珟される文字数が倚いほど蚈算効率は高いずいうこずです。

䞀般的な日本語LLMでは、日本語は1トヌクン=1.5文字〜2文字皋床で衚珟されるため、1文字単䜍で区切る堎合に比べお、蚈算効率が1.5倍〜2倍ほど高くなりたす。倚くのLLM開発では蚈算量に比䟋しお膚倧な経枈的コストがかかるため、このような蚈算効率の向䞊は非垞に重芁なポむントであるず蚀えたす。

埌述したすが日本語をバむト列で衚蚘するず1文字あたり3バむトで衚珟されるこずも倚いため、1文字=3トヌクンずなるこずもありたす。よっお、1文字=1トヌクンずなるこずで2倍以䞊の効率化に぀ながるず考えられたす。

たた、トヌクナむズの効率が䞊がるこずは事前孊習だけではなく掚論速床にも寄䞎したす。少し前に公開されたGPT-4oを䜿われおいる方は感じたかもしれたせんが、こちらは埓来のGPT-4ず比べお高速にレスポンスを返すようになっおいたす。䞻な芁因は、語圙にほずんど含たれおいなかった日本語も語圙に远加され、同じテキストでもより少ないトヌクンで入力・出力できるようになったこずであるず考えられたす。

1-2. 【先行研究調査】トヌクナむザヌの評䟡指暙

ここからは、GENIACプロゞェクトにおトヌクナむザヌを構築するにあたっお調べたこずに぀いお玹介しおいきたす。私自身、䞊のような基瀎知識はあったもののトヌクナむザヌを䜜るずいう経隓はなかったため、たず「どのようなトヌクナむザヌがよいずされるのか」ずいう疑問がありたした。そこで評䟡指暙に぀いお調べおみたした。

䞀般的な評䟡ずしおは以䞋のようなものがあるこずがわかりたした。

  • Fertility
    • 文章を衚珟するのに必芁なトヌクンの平均数。スコアが高いほどトヌクナむザヌの圧瞮率が䜎いこずを意味する。
  • Parity
    • 異なる蚀語に察しおトヌクナむズしたずきのトヌクン数の長さのバランスを瀺す指暙。スコアが高いほど倚蚀語でのトヌクナむズのバランスがいい。FLORES-200のような倚蚀語翻蚳コヌパスを䜿っお、蚀語間のトヌクン数によっお均等性を比范するこずができる。蚀語Aの文のトヌクン数 / 蚀語Bの文のトヌクン数が1に近いほどParityが高い。
  • LPTLength Per Token
    • 1トヌクンあたりの文字数を意味する。テキストの圧瞮比の逆数で、文字数 / トヌクン数で求める。倧きいほど圧瞮率が高い。LPTは語圙数ず正の盞関をも぀。

1-3. 【先行研究調査】アルゎリズムの違いず䞋流タスクの粟床

代衚的なアルゎリズムずしおBPEずUnigramを䞊げたしたが、倚くのLLMのトヌクナむザヌではこれらのどちらかが甚いられおいたため、チヌムの開発ずしおもどちらかを甚いるこずを怜蚎しおいたした。ただし、どちらが今回の日本語LLM開発に適しおいるかを決定づけるこずが難しく、最終的には参考にしおいた日本語モデルず䜿甚したいラむブラリずの互換性を螏たえお決定したした。

しかし、念のためどのような先行研究があるかは調べおいたした。䞊に挙げたTokenizer Choice For LLM Training: Negligible or Crucial? (2023)では、BPEずUnigramのどちらがLLMの䞋流タスクの性胜が高いかを実隓しおいたした。本論文では英語およびペヌロッパ圏の倚蚀語を扱っおいたため、日本語ではありたせん。結論ずしおは、倚蚀語の堎合はUnigramを䜿った堎合の䞋流タスクの性胜が高いずいうこずが述べられおいたした。

1-4. 【先行事䟋調査】代衚的な日本語モデルのトヌクナむザヌ

トヌクナむザヌを構築するにあたっお、他の日本語を扱うのに適したLLMのトヌクナむザヌに぀いお調査をしおいたした。

モデル 孊習コヌパスの蚀語(*1) 語圙数 アルゎリズム 参考
stabilityai/japanese-stablelm-base-alpha-7b 英語(*2), 日本語 65,536(*3) BPE blog
elyza/ELYZA-japanese-Llama-2-7b 英語(*2), 日本語 45,043(*3) BPE blog
llm-jp/llm-jp-13b-v1.0 英語, 日本語 50,688 Unigram paper
stabilityai/japanese-stablelm-base-ja_vocab-beta-7b 英語(*2), 日本語 49,408(*3) BPE blog
tokyotech-llm/Swallow-7b-hf 英語(*2), 日本語 43,176(*3) BPE paper
rinna/nekomata-14b 英語(*4), 䞭囜語(*4), 日本語 151,936 BPE blog

*1) 䞻ずしお構成されおいる蚀語のみ蚘茉しおいたす。構成比が小さい蚀語やプログラムは省略しおいたす。
*2) Llama 2を継続事前孊習したモデルであるため、Llama 2の事前孊習時で䜿甚されおいるデヌタにおいお英語が䞻であるこずを瀺しおいたす。
*3) Llama 2の語圙を拡匵しおいるため、これらのうち32,000トヌクンはLlama 2ですでに蚭定されおいる語圙になりたす。
*4) Qwen 7Bを継続事前孊習したモデルであるため、Llama 2の事前孊習時で䜿甚されおいるデヌタにおいお英語ず䞭囜語が䞻であるこずを瀺しおいたす。

これをみるず、倚くのモデルがBPEを甚いおいるこずがわかりたすが、これはベヌスずなっおいるLlama 2のトヌクナむザヌがBPEを甚いおいるためです。れロから事前孊習しおいるllm-jp/llm-jp-13b-v1.0ではUnigramが甚いられおおり、このモデルは事前孊習したデヌタの蚀語ドメむンの割合ずしお英語ず日本語をおおおよそ半分ず぀孊習しおいたした。今回のLLM開発で私たちのチヌムも同様に英語ず日本語を1:1皋床の割合で孊習するこずを怜蚎しおいたため、llm-jp/llm-jp-13b-v1.0を参考ずさせおいただきたした。

1-5. 【実装方法】束尟研暙準コヌドの理解

ここからは実装に関する内容に入っおいきたす。
本プロゞェクトでは、運営である束尟・岩柀研究宀から「LLM開発の暙準コヌド・手順」ずしお、サンプルプログラムをご提䟛いただいおいたした。私はトヌクナむザヌの構築を経隓したこずがなかったので、実装に぀いおはたずこちらのコヌドの理解から始めたした。

https://github.com/matsuolab/ucllm_nedo_prod/tree/main

トヌクナむザヌの孊習コヌド

コヌド理解のメモ

  • spm.SentencePieceTrainer.trainずいう蚘茉からわかるように、トヌクナむザヌの構築にはSentencePieceが甚いられおいる。
  • 匕数が様々あるがそれぞれ以䞋のような意味である。党おの匕数に぀いお知りたい堎合はドキュメントを確認。
input=args.input, # むンプット孊習デヌタ
model_prefix=args.model_prefix, # モデルの接頭蟞
vocab_size=args.vocab_size, # 語圙サむズ
input_sentence_size=args.input_sentence_size, # 孊習に䜿う最倧のセンテンス数
shuffle_input_sentence=args.shuffle_input_sentence, # センテンスをシャッフルするかどうか
num_threads=args.num_threads, # 孊習のスレッド数
character_coverage=args.character_coverage, # 文字のカバヌ割合
model_type=args.model_type, # アルゎリズム
train_extremely_large_corpus=args.train_extremely_large_corpus, # Unigramのビット深床を増加させるかどうか倧芏暡コヌパスを䜿う堎合に有効にする
user_defined_symbols=[BOS_TOKEN, 
] # ナヌザヌ定矩トヌクンの蚭定
byte_fallback=True, # バむトフォヌルバック
split_digits=True, # 数字を分割するかどうか
allow_whitespace_only_pieces=True, # 空癜をトヌクンずしお扱うかどうか
remove_extra_whitespaces=False, # 連続する空癜を削陀するかどうか
  • ナヌザヌ定矩トヌクンずしおは、special_token_list.pyにお以䞋のように蚭定されおいる。
UNK_TOKEN = "<unk>"
BOS_TOKEN = "<s>"
EOS_TOKEN = "</s>"
PAD_TOKEN = "<pad>"
CLS_TOKEN = "<CLS>"
SEP_TOKEN = "<SEP>"
EOD_TOKEN = "<EOD>"
MASK_TOKEN = "<MASK>"
NEWLINE_TOKEN = "\n"
  • 以䞋のようなスクリプトでargsを蚭定し぀぀トヌクナむザヌの孊習を実行する。
python train_sentencepiece_tokenizer.py
 --input /path/to/dataset1.jsonl,/path/to/dataset2.jsonl,/path/to/dataset3.jsonl \
    --model_prefix ${YOUR_TOKENIZER_NAME} \
    --vocab_size 32000 \
    --input_sentence_size 3000000 \
    --shuffle_input_sentence True \
    --num_threads 16
  • 孊習した埌、SentencePieceからHuggingFace型ぞ倉換する。倉換コヌドは以䞋。

https://github.com/matsuolab/ucllm_nedo_prod/blob/16c3b3b9cbc93f43b68d9616a6976b527bfe8cd2/train/scripts/step3_upload_pretrained_model/convert_tokenizer_from_sentencepiece_to_huggingface_transformers.py

  • from transformers import T5TokenizerでT5Tokenzierずいうクラスを取埗しお、モデルファむルや各皮蚭定を枡しおHuggingFaceのtransformers`ラむブラリで扱えるトヌクナむザヌに倉換しおいるず読み取れる。

この時点で各皮匕数の意味などを党お理解するこずはできたせんでしたが、党䜓的な実装の流れは掎むこずができたした。これをもずに意図通りの挙動をするかなどを実隓的に確かめながらトヌクナむザヌ構築するこずずしたした。

前半パヌトの「1. トヌクナむザヌ構築の取り組みを通じお孊んだこず」はここたでずしたす。聞いたこずも䜿ったこずもあるが仕組みをよく知らなかったずいう方も、ここたでの内容である皋床の基瀎知識を抑えるこずができおいれば幞いです。

2. チヌムの取り組み玹介

ここからはチヌムのLLM開発で実際に取り組んだトヌクナむザヌ構築に぀いお玹介したいず思いたす。

2-1. どのようなトヌクナむザヌを䜜るか

たずどのようなトヌクナむザヌを䜜るかを怜蚎したした。特に倧きなポむントずしおは以䞋が挙げられたした。

語圙数

䞊述した通り、トヌクナむザヌは語圙数が倧きなポむントになりたす。先行事䟋調査で芋たように圓時の日本語LLMの倚くが玄50,000語の語圙を蚭定しおいたした。そのため、私たちのチヌムでも同等サむズにするこずずしたした。ただし、圓時はQwen玄15䞇語やGemma玄25䞇語ずいった高性胜なモデルがちょうど登堎し始めた頃であり、自分の䞭では「語圙サむズが倧きい方がLLMの性胜が䞊がるのか」ずいう仮説が出おきおいたした。そこで、最終的には50,000語より少しだけ倚く、56,320語に蚭定したした。

たた、語圙の内蚳に぀いおですが、䞊に挙げたLlama 2を継続事前孊習したモデルでは32,000語に玄20,000語のトヌクンを远加しおいるため、英語ず日本語の比率はおおよそ3:2ずなっおいたす。ただし、これらのモデルはあくたでもLlama 2の事前孊習がメむンであったため、英語のトヌクンの割合が倚いこずは自然であるように思いたす。

私たちのチヌムでは、英語ず日本語のテキストを同じくらいの量で事前孊習を行う予定であったこずから、トヌクナむザヌの語圙も1:1皋床を想定しおトヌクナむザヌを孊習しようず考えたした。しかし、日本語テキストで獲埗した語圙にも倚くのアルファベットが含たれおいるため、結果的には英語ず日本語の語圙は3:2に近い割合になりたした。

アルゎリズム

こちらは䞊述した通り、Unigramを甚いおいたす。先行研究の「ペヌロッパ圏での倚蚀語の堎合にはUnigramの方が䞋流タスクの性胜が高かった」ずいう瀺唆や、llm-jp/llm-jp-13b-v1.0を参考にしおいたずいう理由のほか、llm-jp-tokenizerずいうリポゞトリが耇数蚀語の語圙の割合を調敎しながらトヌクナむザヌを構築するのに圹立ちそうであり、こちらがUnigramベヌスの方法ずなっおいたこずから、Unigramを䜿うこずずしたした。

孊習デヌタ

トヌクナむザヌの孊習にはできるだけ敎頓されたテキストであるこずが望たしいため、䞻にはwiki40bを甚いたした。日本語ず英語のどちらも敎備されおいたため、䞡蚀語ずもこちらのテキストデヌタをメむンで孊習に䜿いたした。たた、今回のLLM開発の評䟡タスクにはプログラム生成タスクや数孊タスクも含たれるこずがわかっおいたため、語圙にもコヌド特有の英語や数孊蚘号を含めるこずを目的ずしお、事埌孊習に利甚しようず考えおいたMBPPやGSM8Kも語圙獲埗に䜿うこずずしたした。

2-2. 実装に぀いお

党䜓像

党䜓的な流れずしおは以䞋のように、各ドメむンコヌパスで別々にトヌクナむザヌを孊習しお語圙を獲埗し、任意の割合で統合しお最終的な語圙ずモデルを䜜るずいう手順で進めたした。

スクリプト

チヌムのリポゞトリにおスクリプトを公開しおいたす。
基本的には束尟研LLM開発暙準コヌドずllm-jp-tokenizerを参考にさせおいただいおいたす。

https://github.com/geniacllm/tokenizer/tree/main

モデル

チヌムのHuggingFaceにおトヌクナむザヌを公開しおいたす。
https://huggingface.co/geniacllm/ja-en-tokenizer-unigram-v5

実装手順

孊習甚デヌタが準備できおいる前提で説明したす。

1. 日本語テキストの圢態玠解析

日本語に぀いおは事前に圢態玠解析の凊理を含めるこずで、圢態玠の倧きさ以䞊でトヌクンが区切られないようにしたした。実装方法ずしおは、fugashiを甚いお圢態玠ごずに||||ずいう文字列で分割を行いたした。こちらの実装はSentencepiece の分割を MeCab っぜくするずいう蚘事を参考にさせおいただきたした。

圢態玠解析を行ったテキストに察しお以䞋のようにSentencePieceでpretokenization_delimiter="||||"ず蚭定するこずで、||||ずいう区切りを䜿った分割を適甚するこずができたす。

spm.SentencePieceTrainer.train(
        input=args.input, # むンプットファむル
        

        pretokenization_delimiter="||||"
    )

2. 蚀語ごずにトヌクナむザヌを孊習

䞊の通り、英語・日本語・算数・コヌドのそれぞれでトヌクナむザヌを孊習しお語圙を獲埗したした。
英語算数およびコヌドず日本語の孊習の蚭定ずしお倉えた郚分ずしおは、先ほどのpretokenization_delimiter="||||"の蚭定以倖では1トヌクンに察する最倧の文字数の蚭定がありたす。

spm.SentencePieceTrainer.train(
        

        max_sentencepiece_length=16, # 最倧の長さ
        

)

日本語では8文字、英語では16文字ずしお蚭定したした。

そのほか、蚭定ずしおは以䞋のようにしおいたした。䞀郚のみ抜粋

vocab_size=56,320 # 語圙サむズ
character_coverage=0.9995 # 文字のカバヌ率99.95%
model_type="unigram" # アルゎリズム
normalization_rule_name="identity" # 正芏化なし
byte_fallback=True # バむト倉換あり
split_digits=True # 数字分割あり
allow_whitespace_only_pieces=True # 空癜のトヌクンを蚱可する
remove_extra_whitespaces=True # 䜙分な空癜の削陀あり

ここたでで説明しおいたせんでしたが、重芁である匕数を3぀取り䞊げお觊れおおきたす。

normalization_rule_name(正芏化)

こちらは文字通り「正芏化」の蚭定です。identityかnmt_nfkcを蚭定でき、前者は正芏化なし、埌者はNFKC正芏化ありを意味したす。NFKC正芏化ずは「互換等䟡」で文字を分解し「再合成」する正芏化凊理です。参考[Unicode 正芏化](https://ja.wikipedia.org/wiki/Unicode正芏化)
正芏化ありの堎合、䟋えばThisずthis、テストず、1ずが同じトヌクンずしお扱われたす。

llm-jp/llm-jp-13b-v1.0やtokyotech-llm/Swallow-7b-hfのトヌクナむザヌで詊したずころ、䞊の䟋は党お別のトヌクンずしお扱われおいたため、おそらく正芏化なしの蚭定であるず考えられたした。今回の私たちの開発目的を螏たえおも、正芏化なしによるメリットの方が倚いず考えられたため、normalization_rule_name="identity"ずしたした。

byte_fallbackバむトフォヌルバック

基瀎線で芋おきたように、トヌクナむザヌは語圙に含たれおいるトヌクンをベヌスずしお分割
゚ンコヌド・デコヌドを行いたす。逆に蚀えば、語圙に含たれおいないトヌクンを扱うこずができず、<unk>ずいったトヌクンに眮き換えたりしたす。この問題を解決するために、バむトフォヌルバックずいう機胜がありたす。これは、扱いたい文字が語圙に含たれおいないずき、その文字をバむト列通垞はUTF-8のバむト列ずしお倉換するものです。

䟋えば、語圙に挢字のトヌクンをほずんど含んでいないmeta-llama/Llama-2-7b-hfのトヌクナむザヌに日本語をトヌクナむズさせおみるず以䞋のようになりたす。

tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-hf")
print(tokenizer.tokenize("トヌクナむザヌ構築のナレッゞずチヌムの取り組み玹介"))
# 出力
['▁', 'ト', 'ヌ', 'ク', 'ナ', 'ã‚€', 'ザ', 'ヌ', '<0xE6>', '<0xA7>', '<0x8B>', '<0xE7>', '<0xAF>', '<0x89>', 'の', 'ナ', 'レ', 'ッ', 'ゞ', 'ず', 'チ', 'ヌ', 'ム', 'の', '取', 'り', '<0xE7>', '<0xB5>', '<0x84>', 'み', '<0xE7>', '<0xB4>', '<0xB9>', '介']

これをみるず、構築が'<0xE6>', '<0xA7>', '<0x8B>', '<0xE7>', '<0xAF>', '<0x89>'ずいうバむト列に倉換されおいるこずがわかりたす。語圙には構や築ずいった挢字が含たれおいないため、バむト列に倉換するこずで未知語に察応しおいたす。逆にテキストをデコヌドするずきにはこのバむト列を甚いるこずで、構築ずいう挢字の文字列を出力するこずができたす。このように、挢字2文字を6トヌクンで衚珟しおいるため効率は悪いですが、バむトフォヌルバックを䜿うこずで、未知語を扱うこずができたす。

split_digits数字の分割

こちらはドキュメントに"split all digits (0-9) into separate pieces"ず蚘茉があるように、数字を分割するかどうかの蚭定です。今回はTrueに蚭定したした。理由ずしおは、数を衚珟する際に桁数が揃わないこずがLLMの性胜に圱響を及がす可胜性があるずいった知芋が芋られたためです。

3. 語圙の远加・敎備

ここたでで、各蚀語に察する語圙が獲埗できたした。実際に䞭身を芋おみるず、日本語に぀いおはカタカナ語が倚く、䞀般的に利甚する語や衚珟があたり含たれおいないこずがわかりたした。䞊述した反省点の通り、倚様なテキストを䜿うこずで防ぐこずができたかもしれたせん。

そこで、日本語および蚘号等を䞭心に手動で語圙を远加するこずずしたした。

  • 䞀般語
    • wikitionary 「日本語の基本語圙1000」を抜出しお远加。
    • wikitinary を参考に名詞・圢容詞など品詞ごずによく䜿うものを定性的に遞別しお远加。
    • 文化庁 「垞甚挢字䞀芧衚」の䟋から䞀郚をサンプリングしお远加。
    • 時間・季節・方角に関する語を远加。
    • 郜道府県・芳光地・東京23区などの土地の名称を远加。
    • 頻出する日本の苗字を远加。
  • 定型衚珟
    • 「こんにちは」「よろしく」「ございたす」などの定型的な衚珟を远加。
  • 数字および蚘号
    • 挢数字
    • 半角数字0~9
    • 党角数字〜
    • 䞊付き数字0〜9
    • 数孊に出おくるギリシャ文字
    • 「(」や「*」などよく䜿われる蚘号
    • 様々な長さの空癜の連続プログラム生成などで圹に立ちたす。

蚘憶が定かではありたせんが、合蚈で数千〜1䞇ほどの語圙を远加したかず思いたす。

語圙を远加した埌に重耇を削陀し、スコアの䜎いトヌクンを削陀するこずで、任意の語圙数に調敎したした。

4. スコアの再掚定

各蚀語で語圙を獲埗しおマヌゞした埌は、Unigramによっお各トヌクンに察しおスコア付けを行いたす。詳现はスクリプトをご参照いただければず思いたすが、語圙を蚭定しおテキストからスコアを再孊習するこずでスコア掚定ができたす。ここでは各蚀語のテキストを党お䜿いたした。

5. 語圙からトヌクナむザヌ構築

䞊のステップたでで、語圙ず各トヌクンに察するスコア付䞎ができたした。これを䜿っおトヌクナむザヌのモデルファむルを䜜る必芁がありたすが、こちらはllm-jp-tokenizerのこちらのスクリプトを䜿甚させおいただきたした。

6. HuggingFaceのトヌクナむザヌ型ぞ倉換

束尟研LLM開発暙準コヌドにあったように、HuggingFaceのクラスに適甚するトヌクナむザヌずする必芁がありたす。暙準コヌドではT5Tokenizerのクラスが䜿われおいたした。しかし、私たちのチヌムではdMoEずいうMoEアヌキテクチャのLLMを構築する方針ずしおおり、Mixtralベヌスのモデルを䜜るずいう流れになっおいたした。ここで、本家mistralai/Mixtral-8x7B-v0.1はLlamaTokenizer圢匏を䜿甚しおいるこずがわかったため、チヌムにおいおもT5TokenizerではなくLlamaTokenizerでトヌクナむザヌを䜜るこずずしたした。

実装はシンプルで、暙準コヌドのT5TokenizerをLlamaTokenizerにするだけでしたが、暙準コヌドで1点泚意が必芁なこずがありたした。それは、クラスの蚭定の䞀぀であるsplit_special_tokensがTrueになっおいたこずです。こちらは特殊トヌクンを分割するかどうかの蚭定ですが、Trueの堎合、特殊トヌクンが分割されおしたうため、文頭トヌクンや文末トヌクンが意図しおいた䞀぀のトヌクンIDでは扱われないこずになりたす。

tokenizer.split_special_tokens=True
tokenizer.tokenize("これはトヌクナむザヌのテストです。 </s>")

tokenizer.split_special_tokens=False
tokenizer.tokenize("これはトヌクナむザヌのテストです。 </s>")
# 出力
['▁', 'これは', 'ト', 'ヌク', 'ナ', 'ã‚€', 'ザヌ', 'の', 'テスト', 'です', '。', '▁</', 's', '>']
['▁', 'これは', 'ト', 'ヌク', 'ナ', 'ã‚€', 'ザヌ', 'の', 'テスト', 'です', '。', '▁', '</s>']

䞊の文章をみるず、文末トヌクン</s>が分割されおしたっおいるこずがわかりたす。本来1トヌクンで凊理したいのに察しお3トヌクン䜿うこずになっおしたう䞊に、文末ずしおの意味を持たせるずいう目的も達成できない恐れがあるため、泚意が必芁です。

以䞊が、トヌクナむザヌの構築手順でした。

2-3. 苊劎した点

事前・事埌孊習ラむブラリずの敎合性の調査

䞊でT5TokenizerずLlamaTokenizerを出したしたが、このようなトヌクナむザヌのクラスごずの倧きな違いの䞀぀ずしお「特殊トヌクンの蚭定」がありたす。それは文頭トヌクンが<s>であるかずいった文字列そのものの違いもありたすが、これはさほど倧きな問題ではありたせん。重芁なのは「テキストを゚ンコヌドしたずきにデフォルトで蚭定される特殊トヌクンは䜕か」ずいう点です。

䟋えば、meta-llama/Llama-2-7b-hfずllm-jp/llm-jp-13b-v1.0のトヌクナむザヌの違いを芋おみたす。

from transformers import AutoTokenizer

tokenizer_A = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-hf")
tokenizer_B = AutoTokenizer.from_pretrained("llm-jp/llm-jp-13b-v1.0")

print("="*30, "Llama 2", "="*30, )
print(tokenizer_A.tokenize("This is a pen.", add_special_tokens=True))
print(tokenizer_A.encode("This is a pen.", add_special_tokens=True))

print("="*30, "LLM-jp", "="*30, )
print(tokenizer_B.tokenize("This is a pen.", add_special_tokens=True))
print(tokenizer_B.encode("This is a pen.", add_special_tokens=True))
# 出力
============================== Llama 2 ==============================
['<s>', '▁This', '▁is', '▁a', '▁pen', '.']
[1, 910, 338, 263, 6584, 29889]
============================== LLM-jp ==============================
['▁This', '▁is', '▁a', '▁', 'pen', '.', '<EOD|LLM-jp>']
[406, 316, 311, 31, 7605, 10013, 7]

これをみるず、同じコヌドを実行しおいたすが、meta-llama/Llama-2-7b-hfの方は文頭トヌクンである<s>が付䞎されおいるのに察しお、llm-jp/llm-jp-13b-v1.0の方は文末トヌクンである<EOD|LLM-jp>が付䞎されおいるこずがわかりたす。トヌクナむザヌの蚭定はtokenizer_config.jsonのtokenizer_classから確認できたすが、これを芋るずそれぞれLlamaTokenizerずPreTrainedTokenizerFastずいうクラスが䜿われおいるこずがわかりたす。

したがっお、䜿われおいるトヌクナむザヌのクラスによっお、゚ンコヌド時の特殊トヌクンの付䞎の蚭定は異なるずいうこずです。

これがなぜ問題であり苊劎に぀ながったかずいうず、このような特殊トヌクン付䞎の蚭定は、事前孊習や事埌孊習のラむブラリにおけるスクリプトず深く関係するためです。

䟋えば、私たちのチヌムで最終的に䜿甚した孊習ラむブラリであるLlaMA2-Accessoryでは、゚ンコヌド凊理が以䞋のように定矩されおいたした。

def encode(self, s: str, bos: bool, eos: bool) -> List[int]:
        assert type(s) is str
        if self.tokenizer_type == "transformers":
            t = self.tokenizer.encode(s, truncation=False, add_special_tokens=False)
        else:
            t = self.tokenizer.encode(s)
        if bos:
            t = [self.bos_id] + t
        if eos:
            t = t + [self.eos_id]
        return t

こちらを芋るず、tokenizer.encode(s, truncation=False, add_special_tokens=False)ずなっおいるこずから、゚ンコヌド時に特殊トヌクンは付䞎されないこずがわかりたす。ただし、encodeメ゜ッドにbosずeosの匕数を指定するこずができ、それぞれTrueの堎合に付䞎されるように曞かれおいたす。実際にこれらの匕数がどのように蚭定されお実行されおいるかを確認するためには、孊習スクリプトを芋に行く必芁がありたすが、結論ずしおはinput_data = torch.tensor(self.tokenizer.encode(ann, bos=True, eos=True), dtype=torch.int64)ずしお、テキストに察しお文頭トヌクンず文末トヌクンを䞡方ずも付䞎しお凊理した埌に事前孊習に進んでいるこずがわかりたした。

このような凊理を確認しない堎合、<s><s>ずいうように意図せず連続で付䞎しおいたり、そもそも特殊トヌクンが付䞎されおいなかったりずいった事象が発生する恐れがありたす。 私たちのチヌムでは事前孊習の本番が開始するたで、いく぀かのラむブラリで怜蚎をしおいたため、党おに察しおのトヌクナむザヌの凊理を読み解きに行く必芁があり、倧倉苊劎したした。

同じく事埌孊習ラむブラリに぀いおもいく぀か怜蚎しおいたため同じような苊劎がありたした。事埌孊習では、入力トヌクンをマスクしお出力郚分のみを損倱蚈算するずいった凊理をするのが䞀般的であるため、その区切り䜍眮での文末トヌクンの凊理がどうなっおいるかずいった点の読み解きも必芁でした。

䜕が最適かわからないずいう難しさ

苊劎したこずずは若干異なりたすが、粟神的にき぀かったこずずしおは、「自分の䜜っおいるトヌクナむザヌが本圓にLLMの性胜向䞊に寄䞎しおいるのか」ずいう疑問が最埌たで拭えなかったこずです。もちろん事前孊習を䜕床も行っおトヌクナむザヌの違いによる粟床を確かめるずいうこずはできないため、「先行研究や他の事䟋を参考にするず、おそらくこうするのがよさそう」ずいう皋床の根拠で進んでいたこずが、個人的にはメンタルが削られる郚分でした。

最近ではScaling Laws with Vocabulary: Larger Models Deserve Larger Vocabularies (2024)で最適な語圙サむズに぀いお研究されおいるように、LLMの性胜に寄䞎するトヌクナむザヌのベストプラクティスは研究され続けおいくず思いたすので、匕き続き孊んでいこうず思いたす。

2-4. その他 実装を通しお孊んだこず

Chat Template

HuggingFaceのトヌクナむザヌにはChat templateずいうものが蚭定できたす。これは、指瀺圢匏や察話圢匏でファむンチュヌニングする際の圢匏の統䞀や、その圢匏で掚論するためにプロンプトに指定のテンプレヌトを自動適甚するための機胜だず思われたす。

䟋えば、mistralai/Mistral-7B-Instruct-v0.1では以䞋のように動䜜させるこずができたす。

from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-Instruct-v0.1")

chat = [
  {"role": "user", "content": "Hello, how are you?"},
  {"role": "assistant", "content": "I'm doing great. How can I help you today?"},
  {"role": "user", "content": "I'd like to show off how chat templating works!"},
]

print(tokenizer.apply_chat_template(chat, tokenize=False))
# 出力
<s>[INST] Hello, how are you? [/INST]I'm doing great. How can I help you today?</s> [INST] I'd like to show off how chat templating works! [/INST]

この䟋では、chatずしお蟞曞が入ったリストを䜜っおいたす。roleずcontentを䞎えおapply_chat_templateメ゜ッドを䜿うず、特殊トヌクンを付䞎した状態で察話圢匏のようにテキストを倉換しおくれたす。

このテンプレヌトはtokenizer_config.jsonで蚭定するこずができたす。実際にjsonファむルを芋おみるず、以䞋のように定矩されおいたす。

"chat_template": "{%- if messages[0]['role'] == 'system' %}\n    {%- set system_message = messages[0]['content'] %}\n    {%- set loop_messages = messages[1:] %}\n{%- else %}\n    {%- set loop_messages = messages %}\n{%- endif %}\n\n{{- bos_token }}\n{%- for message in loop_messages %}\n    {%- if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}\n        {{- raise_exception('After the optional system message, conversation roles must alternate user/assistant/user/assistant/...') }}\n    {%- endif %}\n    {%- if message['role'] == 'user' %}\n        {%- if loop.first and system_message is defined %}\n            {{- ' [INST] ' + system_message + '\\n\\n' + message['content'] + ' [/INST]' }}\n        {%- else %}\n            {{- ' [INST] ' + message['content'] + ' [/INST]' }}\n        {%- endif %}\n    {%- elif message['role'] == 'assistant' %}\n        {{- ' ' + message['content'] + eos_token}}\n    {%- else %}\n        {{- raise_exception('Only user and assistant roles are supported, with the exception of an initial optional system message!') }}\n    {%- endif %}\n{%- endfor %}\n",

芋慣れない曞き方だず思いたすが、こちらはJinjaず呌ばれる蚘法で曞かれおいるようです。詳现はこちらのドキュメントをご参照ください。

実際に任意のテンプレヌトを䜜っおみたす。
ここでは、よくむンストラクションチュヌニングで甚いられるalpaca圢匏の日本語版をテンプレずしお䜜っおみたいず思いたす。

むメヌゞずしおは以䞋のような圢匏を䜜れればOKです。

以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい。

### 指瀺:
{text}

### 入力:
{text}

### 応答:

これをjinjaテンプレヌトで䜜るず、以䞋のように曞けたす。これをtokenizer_config.jsonに远蚘したす。この蚘法ぞの倉換はChatGPTなどを䜿うず楜です。

"chat_template":"{% for message in messages %}{% if message['role'] == 'system' %}{{ message['content'] + '\n\n'}}{% elif message['role'] == 'instruction' %}{{ '### 指瀺:\n' + message['content'] + '\n\n'}}{% elif message['role'] == 'input' %}{{ '### 入力:\n' + message['content'] + '\n\n'}}{% endif %}{% endfor %}### 応答:\n\n"

テンプレの挙動確認をしおみたす。

from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("you/your_tokenizer_name")

messages = [
    {"role": "system", "content": "以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい。"},
    {"role": "instruction", "content": "GENIACプロゞェクトの䞀぀である東京倧孊束尟研究宀のLLM開発プロゞェクトに぀いお説明しおください。"},
    {"role": "input", "content": "GENIACずは経枈産業省による日本の生成AI開発力向䞊のための取り組みです。"},
]

print(tokenizer.apply_chat_template(messages, tokenize=False))
以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい。

### 指瀺:
GENIACプロゞェクトの䞀぀である東京倧孊束尟研究宀のLLM開発プロゞェクトに぀いお説明しおください。

### 入力:
GENIACずは経枈産業省による日本の生成AI開発力向䞊のための取り組みです。

### 応答:

テンプレヌト通りにテキストが䜜られるこずが確認できたした。

基本的なトヌクナむザヌのメ゜ッド

最埌に、トヌクナむザヌの開発を通しお、HuggingFaceのtokenizerクラスにおいおよく䜿うメ゜ッドを芚えるこずができたしたのでそちらを玹介したす。LlamaTokenizerやT5Tokenizerなどクラスによっお䜿えるメ゜ッドが異なる堎合があるので泚意しおください。

以䞋はelyza/ELYZA-japanese-Llama-2-7b-fastのLlamaTokenizerを䜿った䟋ずなっおいたす。

tokenizer.tokenize

こちらは本蚘事でも䜿っおいた通り、テキストをトヌクン単䜍に分割するメ゜ッドです。
add_special_tokens=Trueずするこずで特殊トヌクンが付䞎されたす。

tokenizer.tokenize("これはトヌクナむザヌのテストです。")
tokenizer.tokenize("これはトヌクナむザヌのテストです。", add_special_tokens=True)
# 出力
['▁', 'これは', 'ト', 'ヌク', 'ナ', 'ã‚€', 'ザヌ', 'の', 'テスト', 'です', '。']
['<s>', '▁', 'これは', 'ト', 'ヌク', 'ナ', 'ã‚€', 'ザヌ', 'の', 'テスト', 'です', '。']
tokenizer.encode

こちらも本蚘事で䜿っおいた通り、テキストをトヌクン単䜍に分割しおID倉換するメ゜ッドです。
tokenizeメ゜ッドず異なる点ずしおは、add_special_tokens=Trueがデフォルトずなっおいたす。そのため、add_special_tokens=Falseずするこずで特殊トヌクンなしでの゚ンコヌドがされたす。以䞋の䟋では文頭トヌクンの<s>がIDが1なので、それが消えおいるこずがわかりたす。さらにreturn_tensors="pt"ず蚭定するこずで、テン゜ル圢匏で返すこずができたす。

tokenizer.encode("これはトヌクナむザヌのテストです。")
tokenizer.encode("これはトヌクナむザヌのテストです。", add_special_tokens=False)
tokenizer.encode("これはトヌクナむザヌのテストです。", return_tensors="pt")
# 出力
[1, 29871, 43061, 30279, 43004, 30576, 30260, 43349, 30199, 44698, 42669, 30267]
[29871, 43061, 30279, 43004, 30576, 30260, 43349, 30199, 44698, 42669, 30267]
tensor([[    1, 29871, 43061, 30279, 43004, 30576, 30260, 43349, 30199, 44698, 42669, 30267]])
tokenizer.encode_plus

こちらはテキストを゚ンコヌドしたものを蟞曞型で返すこずができ、input_idsやattention_maskがキヌずなりたす。実はtokenizer()でそのたたテキストを枡すこずもできたすが、そちらの出力ずencode_plusの出力は同じものずなりたした。

tokenizer.encode_plus("これはトヌクナむザヌのテストです。")
tokenizer("これはトヌクナむザヌのテストです。")
# 出力
{'input_ids': [1, 29871, 43061, 30279, 43004, 30576, 30260, 43349, 30199, 44698, 42669, 30267], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}
{'input_ids': [1, 29871, 43061, 30279, 43004, 30576, 30260, 43349, 30199, 44698, 42669, 30267], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}
tokenizer.batch_encode_plus

こちらは耇数のテキストをたずめお゚ンコヌドするこずができたす。

tokenizer.batch_encode_plus([
    "これはトヌクナむザヌのテストです。", 
    "これはトヌクナむザヌのテストではありたせん。"
], padding='max_length', max_length=16, return_tensors='pt')
# 出力
{'input_ids': tensor([[    1, 29871, 43061, 30279, 43004, 30576, 30260, 43349, 30199, 44698,
         42669, 30267,     0,     0,     0,     0],
        [    1, 29871, 43061, 30279, 43004, 30576, 30260, 43349, 30199, 44698,
         43418, 30267,     0,     0,     0,     0]]), 'attention_mask': tensor([[1, 1, 1, 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, 0, 0, 0, 0]])}

こちらをみるずmax_lengthで長さを揃えお、長さを満たさない郚分は0でパディングされおいるこずがわかりたす。たた、パディング郚分は孊習などの凊理に䜿わないのが䞀般的なので、attention_maskは0になっおいたす。

逆にトヌクンを指定の長さで切り詰めたい堎合はtruncation=Trueず蚭定したす。切り詰めの方向はtokenizer.truncation_sideで蚭定されおいるため、rightかleftで倉曎できたす。以䞋の䟋では最倧8トヌクンになるように巊偎のトヌクンから削陀されおいたす。

tokenizer.truncation_side = "left"
tokenizer.batch_encode_plus([
    "これはトヌクナむザヌのテストです。", 
    "これはトヌクナむザヌのテストではありたせん。"
], padding='max_length', max_length=8, return_tensors='pt', truncation=True)
# 出力
{'input_ids': tensor([[    1, 30576, 30260, 43349, 30199, 44698, 42669, 30267],
        [    1, 30576, 30260, 43349, 30199, 44698, 43418, 30267]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1],
        [1, 1, 1, 1, 1, 1, 1, 1]])}
tokenizer.decode

こちらぱンコヌドず逆の凊理をするデコヌドです。トヌクンIDのリストを枡すこずで文字列に戻すこずができたす。゚ンコヌドず同じように特殊トヌクン付䞎の蚭定がありたすが、匕数ぱンコヌドで䜿ったadd_special_tokensではなくskip_special_tokens=Trueずするこずで、特殊トヌクンを無芖しおデコヌドしたす。

tokenizer.decode([1, 29871, 43061, 30279, 43004, 30576, 30260, 43349, 30199, 44698, 42669, 30267])
tokenizer.decode([1, 29871, 43061, 30279, 43004, 30576, 30260, 43349, 30199, 44698, 42669, 30267], skip_special_tokens=True)
# 出力
"<s> これはトヌクナむザヌのテストです。"
"これはトヌクナむザヌのテストです。"

おわりに

本蚘事ではGENIAC 束尟研LLM開発プロゞェクトのPhase1で取り組んだトヌクナむザヌの構築方法ず、その過皋で埗た孊びに぀いお玹介したした。LLM開発ずいうず事前孊習が本䞞であり最も重芁な郚分であるのは間違いないですが、今回の取り組みを通じお、トヌクナむザヌは事前孊習・事埌孊習・掚論のほが党おに関連する倧事な芁玠であるこずがわかりたした。
たた、私自身はこのプロゞェクトに参加するたでLLM開発やトヌクナむザヌ構築には盎接携わったこずがなかったため、非垞に倚くの孊びがありたした。
改めお、経枈産業省, 東京倧孊束尟・岩柀研究宀, 開発メンバヌ, コミュニティメンバヌを含む関係者の皆様に、この堎をお借りしお感謝申し䞊げたいず思いたす。ありがずうございたした。

東倧束尟・岩柀研究宀 | LLM開発 プロゞェクト[GENIAC]

Discussion