はじめに
生成AIやRAGについて勉強していると、次のような言葉が頻繁に登場します。
- Token
- Embedding
- ベクトル
- Transformer
- Attention
- 行列
- Logits
- Softmax
- 確率分布
それぞれの用語を個別に調べると、なんとなく意味は分かります。
しかし、
「結局、これらはどうつながっているの?」
と聞かれると、意外と説明するのが難しいのではないでしょうか。
私自身も、
- Embeddingは文章をベクトル化するもの
- TransformerはLLMで使われているもの
- Softmaxは確率を計算するもの
という個別の知識は持っていました。
しかし、
文章が入力されてから、どうやって次のTokenが生成されるのか
という一連の流れとして理解できていませんでした。
そこで本記事では、
文章
↓
Token
↓
Embedding
↓
ベクトル
↓
Transformer / Attention
↓
内部表現
↓
Logits
↓
Softmax
↓
次Tokenの確率分布
という流れを一本につなげて理解していきます。
最初はできるだけ専門用語を使わず説明し、その後で専門用語や数式を使って掘り下げていきます。
1. まずは子どもでも分かるように考えてみる
例えば、AIに次の文章を入力したとします。
私は今日、カレーを
人間なら、
食べました
作りました
などの続きを想像できると思います。
LLMも、大まかにいえば
「ここまでの文章を見たとき、次に何が来そうか?」
を予測しています。
ただし、コンピュータは人間と同じように「カレー」という文字をそのまま理解しているわけではありません。
そこで、いくつかの処理が必要になります。
1.1 文章を小さな部品に分ける
まず、文章を小さな単位に分割します。
イメージとしては、
私は今日、カレーを
を、
私
は
今日
、
カレー
を
のような部品に分けます。
この小さな単位を Token(トークン) と呼びます。
実際のTokenは必ずしも「1単語 = 1Token」になるわけではありません。
ここでは、
文章をAIが扱いやすい小さな部品に分割する
くらいの理解で大丈夫です。
1.2 言葉を数字に変える
コンピュータが計算するためには、最終的に言葉を数字として扱う必要があります。
そこでTokenを、例えば次のような数値の並びとして表現します。
カレー
↓
[0.82, -0.31, 0.15, 0.72, ...]
もちろん、実際にはもっと多くの数字が使われます。
このような、
[0.82, -0.31, 0.15, 0.72, ...]
という数値の並びを ベクトル と呼びます。
LLMでは、Tokenに対応するベクトル表現を使って計算を行います。
このように、Tokenなどをベクトルで表現する考え方を Embedding と呼びます。
1.3 周りの言葉との関係を考える
しかし、言葉を数字に変えただけでは十分ではありません。
例えば、
銀行にお金を預ける
という文章と、
川の銀行
という文章があったとします。
日本語では後者は不自然ですが、英語の bank には「銀行」と「川岸」という意味があります。
つまり、同じTokenでも、
周囲にどのようなTokenが存在するか
によって意味が変わります。
そこでLLMは、周囲のTokenとの関係を考えます。
例えば、
銀行 ←→ お金
銀行 ←→ 預ける
といった関係です。
これを行う重要な仕組みが Attention です。
1.4 次に来そうなTokenに点数をつける
周囲の文章を考慮した結果、AIが次のToken候補に次のような点数を付けたとします。
食べた 3.2
作った 2.5
注文した 1.7
走った -1.0
この段階では、まだ「確率」ではありません。
単なるスコアです。
このスコアを Logits(ロジット) と呼びます。
1.5 点数を確率に変える
Logitsのままでは、
「結局、それぞれどのくらい可能性があるの?」
ということが直感的には分かりません。
そこで、これらのスコアを確率分布に変換します。
先ほどのLogitsの場合、おおよそ、
食べた 58%
作った 29%
注文した 13%
走った 1%
のようになります。
※四捨五入しているため、表示上の合計が100%にならない場合があります。
この変換を行う代表的な関数が Softmax です。
1.6 Tokenを1つ選ぶ
ここまで来ると、
食べた 58%
作った 29%
注文した 13%
走った 1%
という確率分布ができました。
この確率分布などをもとに次のTokenを選択します。
例えば、
食べた
が選択されたとします。
すると、
私は今日、カレーを食べた
となります。
しかし、ここで終わりではありません。
今度は、
私は今日、カレーを食べた
をもとに、
「その次には何が来る?」
を再び計算します。
これを何度も繰り返します。
私は今日、カレーを
↓
食べた
↓
。
このように次のTokenを繰り返し予測することで、文章が生成されていきます。
2. 今までの説明を専門用語に置き換えてみる
ここまでの内容を専門用語に置き換えると、次のようになります。
文章
↓
Tokenization
↓
Token
↓
Token ID
↓
Embedding
↓
ベクトル
↓
Transformer / Attention
↓
文脈を反映した内部表現
↓
Logits
↓
Softmax
↓
次Tokenの確率分布
↓
Tokenを選択
↓
文章へ追加
↓
再び次Tokenを予測
ここからは、それぞれについてもう少し詳しく見ていきます。
3. Tokenとは何か
Tokenとは、LLMが文章を処理するときの基本単位です。
例えば、
私はAIを勉強しています
という文章があった場合、イメージとして、
私
は
AI
を
勉強
して
います
のように分割されます。
ただし、実際のToken分割はモデルやTokenizerによって異なります。
Tokenは内部的には Token ID と呼ばれる整数に対応します。
イメージとして、
"AI" → 1234
"勉強" → 5678
のような形です。
つまり、
文章
↓
Token
↓
Token ID
という変換が行われます。
4. Embeddingとは何か
Token IDはただの整数です。
例えば、
犬 → 100
猫 → 101
車 → 102
だったとしても、
犬 = 100
猫 = 101
だからといって、
猫は犬より1だけ意味が大きい
という意味ではありません。
そこでLLMでは、Token IDに対応するベクトル表現を使います。
例えば、
犬
↓
[0.8, 0.2, -0.5, ...]
猫
↓
[0.75, 0.25, -0.48, ...]
のように表現します。
※ここでは説明を簡単にするため、「犬」「猫」がそれぞれ1つのTokenであると仮定しています。実際のToken分割はモデルやTokenizerによって異なります。
もう少し正確にいうと、LLM内部には Embedding Matrix(埋め込み行列) があり、Token IDに対応するベクトルをそこから取得します。
イメージとしては、
Token
↓
Token ID
↓
Embedding Matrix
↓
Tokenに対応するベクトル
という流れです。
このベクトル表現を Token Embedding と呼びます。
5. ベクトルとは何か
ベクトルとは、簡単に言えば、
複数の数値を一定の順序で並べたもの
です。
例えば、
[0.8, 0.2, -0.5]
は3つの値を持っているので、3次元ベクトルと考えることができます。
実際のAIモデルでは、数百次元、数千次元といった高次元のベクトルが使われます。
Embeddingによって、Tokenはニューラルネットワークで計算できるベクトルとして表現されます。
ここで少し注意したいのが、「Embedding」という言葉です。
Embeddingは文脈によって、
- ベクトル表現そのもの
- ベクトル表現へ変換する仕組み
などを指す場合があります。
この記事では分かりやすさのため、
Token
↓
Embedding
↓
ベクトル表現
というイメージで進めます。
6. Transformerはベクトルに何をしているのか
ここから「行列」が登場します。
Transformerでは、Tokenのベクトルに対して大量の行列演算を行います。
その中でも重要なのが Self-Attention です。
Self-Attentionでは、入力ベクトルから主に、
- Query(Q)
- Key(K)
- Value(V)
という3種類の表現を作ります。
数式では、
Q = XW_Q
K = XW_K
V = XW_V
と表現できます。
X はTokenのベクトル表現を並べたもの、W_Q、W_K、W_V は学習によって得られた重みです。
つまり、
ベクトルや行列に行列を掛けて、新しい表現を作る
という計算が行われています。
7. Attentionとは何か
Attentionでは、
どのTokenをどのくらい参考にするべきか
を計算します。
代表的な式がこちらです。
Attention(Q,K,V)
=
softmax(QK^T / √d_k)V
初めて見ると難しそうですが、ざっくり分解すると、
Q × K^T
↓
Token同士の関連度を計算
↓
Softmax
↓
どのTokenをどれくらい見るか重みにする
↓
× V
↓
必要な情報を取り込む
という処理です。
例えば、
私は銀行にお金を預けた
という文章なら、
銀行 ←→ お金
銀行 ←→ 預けた
といったToken間の関係を考慮できます。
この処理などを何層も繰り返すことで、最初のToken Embeddingから、
文章の文脈を反映した内部表現
へ変換していきます。
文章生成では未来のTokenを見ない
GPTのように、前から順番に文章を生成するLLMでは、Attentionを計算するときに重要なルールがあります。
それは、
まだ生成されていない未来のTokenを見ない
ということです。
例えば、
私は今日、カレーを
の次を予測するときに、まだ生成していない答えを先に見ることはできません。
そのため、未来のTokenを参照できないようにする Causal Mask(因果マスク) という仕組みが使われます。
ここでは、
文章生成では、今までに登場したTokenをもとに次のTokenを予測する
と理解しておけば大丈夫です。
8. ベクトルと行列はどうつながっているのか
ここで、一度整理します。
EmbeddingによってTokenはベクトルとして表現されます。
Token
↓
[0.8, 0.2, -0.5, ...]
Transformerでは、そのベクトルに対して行列演算を行います。
ベクトル
↓
行列を掛ける
↓
新しいベクトル
↓
さらにTransformerで処理
↓
文脈を反映したベクトル
つまり、
ベクトルは情報を表現し、行列はその情報を変換する
と考えると分かりやすいです。
9. Logitsとは何か
Transformerによって文脈を反映した内部表現が作られた後、
次のTokenとして、それぞれの候補がどのくらい適切か
を表すスコアを計算します。
もう少し正確には、Transformerが作った内部表現を LM Headと呼ばれる出力層 に通して、各Token候補のスコアへ変換します。
Transformer
↓
文脈を反映した内部表現
↓
LM Head
↓
Logits
例えば、
食べた 3.2
作った 2.5
注文した 1.7
走った -1.0
のような値です。
これが Logits です。
重要なのは、
Logitsはまだ確率ではない
ということです。
例えば、
3.2
2.5
1.7
-1.0
を全部足しても100%にはなりません。
そこでSoftmaxを使います。
10. Softmaxとは何か
Softmaxは、複数のLogitsを確率分布へ変換する関数です。
数式では、
P_i =
e^{z_i} / Σ_j e^{z_j}
と表されます。
z_i が各TokenのLogitです。
例えば、
Logits
食べた 3.2
作った 2.5
注文した 1.7
走った -1.0
にSoftmaxを適用すると、おおよそ、
確率分布
食べた 58%
作った 29%
注文した 13%
走った 1%
のようになります。
※四捨五入しているため、表示上の合計が100%にならない場合があります。
ここで注意したいのが、
Softmaxは「1つを選ぶ関数」ではない
という点です。
Softmaxが行うのは、
Logits
↓
確率分布
への変換です。
その確率分布をどのように使って次のTokenを選択するかは、デコード方法によって変わります。
11. 次のTokenはどう選ばれるのか
最も単純なのは、確率が最も高いTokenを選ぶ方法です。
例えば、
犬 60%
猫 25%
鳥 10%
車 5%
なら、
犬
を選びます。
このような方法は Greedy Decoding と呼ばれます。
一方で、毎回最大確率のTokenだけを選ぶとは限りません。
確率分布に基づいて Sampling する方法もあります。
そのため、LLMでは同じ質問をしても異なる文章が生成される場合があります。
また、生成時の確率分布に影響を与える代表的なパラメータとして Temperature があります。
12. PythonでSoftmaxを確認してみる
ここまでの話を簡単なPythonコードで確認してみます。
import numpy as np
logits = np.array([3.2, 2.5, 1.7, -1.0])
exp_logits = np.exp(logits - np.max(logits))
probabilities = exp_logits / np.sum(exp_logits)
print(probabilities)
Softmaxでは、各値を指数関数に通した後、合計が1になるように正規化します。
なお、
logits - np.max(logits)
としているのは、指数関数の計算で非常に大きな値になってしまうことを防ぐためです。
出力はおおよそ、
[0.58, 0.29, 0.13, 0.01]
となります。
出力された値をすべて足すと、
1.0
になります。
つまり、
Logits
↓
Softmax
↓
0〜1の値
↓
合計1
という確率分布になります。
13. ここまでを一本につなげる
ここまで理解すると、最初の図がかなり違って見えてきます。
文章
│
▼
Tokenization
│
▼
Token
│
▼
Token ID
│
▼
Embedding
│
▼
ベクトル
│
▼
Transformer
│
├─ 行列演算
│
└─ Attention
│
▼
文脈を反映した内部表現
│
▼
LM Head
│
▼
Logits
│
▼
Softmax
│
▼
次Tokenの確率分布
│
▼
Tokenを選択
│
▼
文章へ追加
│
└──────────────┐
│
▼
次Tokenを再び予測
つまり、
EmbeddingでTokenをベクトルとして表現し、Transformerが行列演算やAttentionによってその情報を処理する。その結果から各Token候補のLogitsを計算し、Softmaxによって確率分布に変換する。そして、その分布をもとに次のTokenを選択する。
という一本の流れになります。
14. RAGではこの仕組みがどう使われるのか
ここでRAGとの関係も整理しておきます。
RAGを勉強していると、
文章
↓
Embedding
↓
ベクトル
という処理が出てきます。
そのため、
「LLM内部のEmbeddingとRAGのEmbeddingは同じ話なのか?」
と混乱しやすいポイントです。
RAGでは、まず検索対象となる文書をEmbeddingモデルによってベクトル化します。
【文書登録】
Document
↓
Chunking
↓
Embedding
↓
Vector
↓
Vector DB
例えば、
「私は今日、カレーを……」
という文章を、
[0.12, -0.43, 0.81, ...]
のようなベクトルとして保存します。
ユーザーから質問された場合も同様です。
【検索】
質問
↓
Embedding
↓
質問ベクトル
↓
Vector DBのベクトルと比較
↓
類似度の高い文書を取得
Embeddingモデルでは、意味的に近い文章がベクトル空間上でも近くなるような表現が得られるため、コサイン類似度などを利用して関連文書を検索できます。
例えば、
「犬が好き」 ●
● 「猫が好き」
● 「株価が下落した」
のようなイメージです。
「犬が好き」と「猫が好き」は意味的に関連しているため、ベクトル空間上でも比較的近い表現になることが期待されます。
15. LLM内部のEmbeddingとRAGのEmbeddingの違い
ここまでを見ると、
LLM内部でもEmbeddingを使っているし、RAGでもEmbeddingを使っている
ことが分かります。
ただし、目的が少し違います。
LLM内部では、
Token
↓
Token ID
↓
Token Embedding
↓
Transformer
という流れで、TokenをTransformerが計算できるベクトルとして表現します。
一方、RAGでは、
文章
↓
Embeddingモデル
↓
文章全体の意味を表すベクトル
↓
類似する文章を検索
という目的で使われます。
つまり、
LLM内部のEmbedding
→ TokenをLLM内部で処理するため
RAGのEmbedding
→ 意味の近い文章を検索するため
と考えると分かりやすいです。
16. まとめ
ここまでの内容をもう一度まとめます。
文章
↓
Tokenization
↓
Token
↓
Token ID
↓
Embedding
↓
ベクトル
↓
Transformer
↓
Attention・行列演算
↓
文脈を反映した内部表現
↓
LM Head
↓
Logits
↓
Softmax
↓
次Tokenの確率分布
↓
Tokenを選択
↓
文章へ追加
↓
再び次Tokenを予測
最初は、
- Embedding
- ベクトル
- 行列
- Transformer
- Attention
- Logits
- Softmax
がそれぞれ別の技術に見えていました。
しかし、実際にはすべて、
「次のTokenを予測する」
という1つの処理につながっています。
特に自分の中では、
Embedding
↓
ベクトル
↓
行列演算
↓
Transformer / Attention
↓
内部表現
↓
Logits
↓
Softmax
↓
確率分布
という流れがつながったことで、LLMの仕組みがかなり理解しやすくなりました。
この記事が、同じように
「EmbeddingやTransformerは知っているけれど、それぞれがどうつながっているのか分からない」
という方の理解の助けになれば幸いです。