[Transformer] TokenizerとEmbedding層の動きを追いかける

ディープラーニング-技術のお話し

参考にしたサイトはこちらです。

ちょうどTwitterでぼけーっと眺めているところに、こんなサイトを見つけまして、凄く綺麗にコードが書かれていて非常に読みやすく、原理を理解するのに良い教材じゃないかなと思ってこれ見て学んでみることにしたのです。

トークナイザの動き

トークナイザというのは、NLPモデルに対して、特定のID体系に基づいて文字あるいは単語を数値へ変換する処理です。Hugging Faceを使っていると、たくさんのトークナイザを見ることが出来ます。GPTに関しては、トークナイザのエンコーディング方式について述べたお仕事用ブログ記事が別途あります。

トークナイズされた後のトークンが収容出来る量は、モデル仕様の中で(n-ctx)ト表現されることが多いです。GPT-3だと2048, GPT-3.5だと4096、GPT-4通常モデルであれば8192ですね。

お仕事先で執筆してた記事

さて、このトークナイザですけども、Transformerではこれが扱われる入出力経路は3つありまして。

  • エンコーダから投入するテキスト
  • 学習データとして投入するデコーダ側入力テキスト
  • Transformerが推論して出力する出力テキスト

となってます。

特殊文字

特殊文字というのは、本来の文字とは異なっていて、それ自体がその文章内で何かを意味するトークンを指します。細かい所で異なるものもあるんですが、代表的に紹介されているのはこういうものです。

特殊文字概要・役割
<unk>Unknown Token. 不明なトークンであることを示す。
<pad>長さを揃えるために、文章超が足りない場合に補填するべく使用されるトークン。パディングトークン。
<bos>/<go>Begin of sequence. 文頭を示すトークン。
<eos>End of sequence. 文末を示すトークン。

トークナイズする際に使われる特殊文字

先述した入出力経路に当てはまる文字列について、その処理例が参考URLの中で記載されていました。

■テキスト:エンコーダーに入力するEmbedded
tensor([   2,  386, 1789,  831,   84, 1790, 1791,  828,   31,   26,  178,   12,  252, 1772,   83,  3])

■デコーダーインプット:教師データとして入力するEmbedded
tensor([   2,  166, 1775, 2004,   28,   99,  459, 2005,   65,  537,   40, 1801, 1784,   17,    3])

■デコーダーターゲット:教師データとして出力して欲しいEmbedded。<bos>がない
tensor(      [ 166, 1775, 2004,   28,   99,  459, 2005,   65,  537,   40, 1801, 1784,   17,    3,    1])

投入される文章、つまり入力される文字列は必ず頭に<bos>と<eos>がそれぞれ先頭と末尾に記載されていました。これに対して出力される文字列は先頭の<bos>がなく、<eos>だけがあり、それによって1文字文字数が削られることから末尾に<pad>が追加され、文字列長が揃えられていました。

なお、参考URLのプログラム上では登場するトークンに順にIDを与えるようなトークナイズ処理が行われていました。形態素解析を行い、その単語に合わせてIDを振ってるように見受けられました。ただ、特殊文字が不定だと困るので、最初に与えるものとして、特殊文字を先にID割り当てを行ってるようでした。

以下のものは参考URLから引用させて頂きました。

for i in range(len(texts)):
  j_list.extend(texts[i])
j_counter.update(j_list)
j_v = vocab(j_counter, specials=(['<unk>', '<pad>', '<bos>', '<eos>']))   #特殊文字の定義
j_v.set_default_index(j_v['<unk>'])

こうしてみていて気づいたのは、「GPTはトークンとして英語はきちんと単語単位でトークン化できるが、日本語はほぼ1文字1トークン、場合によっては漢字や一部カタカナは2トークンで表現されている」ところですかね。

となると、GPTはほぼ日本語に関しては1文字単位で特徴量を付与し、それに基づいて推論をさせていると言うことになります。日本語による文字単位の推論って実はもの凄く難しいです。単語であれば、一定の文法に基づいてルール付けられた処理を行わせることが可能ですが、文字単位となると、1文字来たら次に何が来るか、その前に通常何が来るものなのか、あらゆる予測が出来なければなりません。それをいとも簡単にやれてしまうGPT-3以降あるいはその互換モデルには寒気を感じるものがあるほどに非常に難しいものがあるのではないかなと思います。(普通、形態素解析をしてそれをトークンにまわすと思うんだけど・・・・と、ふと思いました)

Embedding-概念を数値化する処理

その次に運ばれるのがEmbedding Layerと呼ばれる層で、送り込まれたそれぞれのトークンがベクトル化されます。ベクトル化されたトークンの次元数は一般的にモデル構造における(d-model)と表現された数値出示されます。GPT-3のDavinciの場合、その入力次元数は12288次元となっており、Davinciモデルに最大2048トークンを突っ込んだ場合、このEmbedding層を抜けた時点でデータは一気に(12288 x 2048)なサイズに膨れ上がります。数値としては、通常32bitな浮動小数点、リソース削減で16bitな浮動小数点を使用することもあります。もちろん、16ビット化した場合は丸め誤差が発生することになり、回答品質に影響を与えることがあります。

これにより、その文字が持つ意味を「数値化された特徴・パターン」として変換され、コンピュータが要約理解出来るようになります。

Positional Encoding-単語位置の登録

次に行われるのがそのトークンがどの位置にあるかという情報をベクトル和にて付与するPositional Encoding(略してPEとも呼ぶらしい)になります。参考URLではこのように書かれていました。

position = torch.arange(max_len).unsqueeze(1).to(device)
div_term = torch.exp(torch.arange(0, dim, 2) * (-math.log(10000.0) / dim)).to(device)
pe = torch.zeros(max_len, 1, dim).to(device)
pe[:, 0, 0::2] = torch.sin(position * div_term)
pe[:, 0, 1::2] = torch.cos(position * div_term)
self.register_buffer('pe', pe)

PEについてよく公式が書かれていますが、その公式を示す部分が上記ハイライト箇所になっています。
PE(position,2i)で表現されるのが pe[:, 0, 0::2]であり、PE(position,2i+1)で表現されるのがpe[:, 0, 1::2]となっています。その前段で行われているのは、そのトークンの位置決定や公式内の要素となる値の計算部分となっています。

これで特徴化した各ベクトル化されたトークンに対して位置情報を加算し、Normレイヤー等でデータが正規化された後、Self-Attention-Layer等に送り込まれることになります。入力された馬鹿でかいベクトルはTransformerのヘッダ数に沿って分散され、処理にまわされるため、ここでGPUの並列処理能力が発揮されるわけですね。

まとめるとこんな感じかな?ということで、図に表現してみました。

テキストが放り込まれてTransformerにたどり着くまで

こういうことを知ると分かってくること

それが本質的に的を得てるのかどうかは分かりませんが、Embeddingで抽出しているデータが日本語だとほぼ1文字であるのに対して、英語だとほぼ確実に1単語となっていることから、特徴をより正確につかんでいるのは英語の方だと理解しました。

よって、日本語でも膨大なデータ量から特徴抽出してるので人として気づきにくいレベルまで高いものがあるのだろうと思うのですが、やっぱり英語でPromptを書いた方がより確実にやらせたいことは出来るんじゃないかという気がします。ただ、日本語にも相応の理解能力は備わっていることから、例えば

  • タスクの説明は英語で記述する
  • ただし、タスクの説明には必ず「出力するPredicted Textは日本語で出せよ」と指示する必要がある
  • 出力に関すること、単語の取り扱いについては日本語で記述する

と言うことをすると、それだけでかなり応答内容の品質は上昇するのではないでしょうかねと言うことです。
現在個人でもお仕事でも取り組んでるLangChainでは、Chainで設定されるタスクには必ず英語でTemplateにタスクの内容が記載されており、例えば主に取り組んでいるRetrievalQAでは英語で「レトリーバーから検索が出来なかった場合は分かりませんとだけ応答しなさい」という命令が加えられてます。QA以外の答えを返しちゃダメ絶対ってここで示してる訳なんですよね。

今後はもうPromptエンジニアリングさえ出来れば良いんだという人もいらっしゃるかと思いますが、中身を知ってその制約や限界を知るだけでもこうしてどこに気を付けたらよりよい結果が生み出されるのか、どこまで意識したら今のところはいいんだろうかという所がわかるんで、たまにはこういった仕組みをきちんと掘り下げてみるって事も重要なのかなと感じる今日この頃です。

Tags:

Comments are closed

PAGE TOP