Chapter 02 · 文章生成の基本

次トークン予測、トークン化、Embedding

AIはどう文章を作っているのか。文字を数字に、数字を意味に変換する——LLMの「言葉の取り扱い」を順を追って見ていく。

生成AI · シリーズ 生成AIの仕組み · 全 5 章

Section 04

AIはどのように文章を生成しているのか

答えは至ってシンプル。「次に来る確率が一番高いトークン(≒単語)をつなげていく」——これを延々と繰り返している。

たとえば「昔々、あるところに、おじいさんと…」という入力に対して、AIは次に来るトークンの確率を計算する。語彙(モデルが知っている全トークン)の中から、それぞれの「来る確率」を弾き出す。

「おばあさん」
90%
「犬」
5%
「ロボット」
1%
「その他多数」
4%

最も高い確率の「おばあさん」が選ばれる(※実際は確率分布から「サンプリング」する場合もある)。そして、選ばれたトークンを入力に追加して、次のトークンをまた予測する。これを愚直に繰り返すことで、長い文章が生まれる

「どう選ぶか」——サンプリングのつまみ

確率分布が出たあと、「どのトークンを選ぶか」にも作法がある。もっとも単純なのが貪欲法(greedy decoding)——毎回いちばん確率の高いトークンを選ぶ方式だ。決定的で再現性が高い反面、文章は単調になりやすく、同じフレーズを何度も繰り返す「劣化(degeneration)」と呼ばれる現象に陥りがちなことが知られている(Holtzman ら 2019)。

そこで実用LLMでは、確率分布からランダムに引くサンプリングに、3つの「つまみ」をかけて出力を制御する。

temperature
0.7
分布の鋭さ。低いほど決定的、高いほど多様
top-p
0.9
累積確率 p% までの「核(nucleus)」だけを候補に
top-k
50
上位 k 個だけを候補に(OpenAI API は未対応)

temperature は確率分布の「とがり方」を変えるつまみで、0 に近いほど最大確率トークンに集中(=ほぼ貪欲法)、1 を超えると分布が平坦になり多様な単語が出やすくなる。top-p(nucleus sampling)は、確率の高い順に足していき累積が p になるまでの候補だけを残す方式。top-k は単純に上位 k 個だけを残す方式。なお OpenAI API では top-p と temperature は使えるが、top-k は公開されていない。

では「もっとも確からしい文」を探したいならビームサーチが良さそうに思えるが、創作・対話系のLLMではほとんど使われない。確率最大化を貫くと、上述の劣化(同じ語の反復、退屈な定型文)が出やすいからだ——機械翻訳のように「正解が狭い」タスクでは有効だが、自由生成では top-p サンプリングのほうが人間らしい文章になる、と Holtzman らが報告している。

Note

同じプロンプトを送っても毎回違う答えが返ってくるのは、内部でサンプリング(確率的なくじ引き)が走っているから。temperature を 0、top-p を 1 に固定すれば貪欲法に近づき、ほぼ同じ出力が再現される(ただし GPU の浮動小数点演算の非決定性により、完全に同一にはならないことがある)。逆に「いつも同じ模範解答」しか欲しくない場面では、温度を下げるのが定石。Source: OpenAI API reference / Holtzman et al. 2019, "The Curious Case of Neural Text Degeneration" (arXiv:1904.09751)

順次生成の様子を見てみよう

01昔々 ある
02昔々 ある ところに
03昔々 ある ところに おじいさん
04昔々 ある ところに おじいさん
05昔々 ある ところに おじいさん と おばあさん
06昔々 ある ところに おじいさん と おばあさん
07…続く…
Note

厳密には「単語」ではなく「トークン」と呼ばれる単位で予測している。トークンは単語より細かい場合もあれば、複数の単語をまとめた単位の場合もある。OpenAIのGPTでは平均して、英語1単語あたり約1〜2トークン、英語の1トークンあたり約4文字に相当する。日本語などの CJK 言語では、英語に比べて 1 文字あたりのトークン数が多くなりやすい(古いトークナイザーでは 1 文字あたり 1〜2 トークン、最近の o200k_base では 1 文字あたり 1 トークン未満になることも)。

Source: OpenAI tiktoken documentation, Hugging Face Tokenization docs

Section 05

トークン化——言葉を数字にする

コンピュータは「数字」しか理解できない。だから、文章をまず数字に変換する必要がある。これがトークン化(Tokenization)と呼ばれる第一ステップだ。

現代のLLMの多くは、BPE(Byte Pair Encoding)という手法、もしくはその派生であるWordPiece、SentencePieceなどでトークン化を行う。BPEは、頻繁に登場する文字の組み合わせを徐々に「1つのトークン」としてまとめていく方法だ。

面白い事実として、BPE はもともと自然言語処理のための手法ではなかった。Philip Gage が 1994 年に C Users Journal 誌で発表したデータ圧縮アルゴリズムが起源で、「最も頻繁に出てくるバイトの組を、新しい 1 バイトに置き換える」という単純な反復によって LZW に匹敵する圧縮率を実現していた。これを NLP に持ち込んだのが Sennrich らの 2016 年論文「Neural Machine Translation of Rare Words with Subword Units」。バイトの代わりに文字や文字列を対象にすれば、未知語をサブワードに分解できる——という発想で、現代 LLM のほぼすべてのトークナイザーがこの系譜上にある。

なぜ「単語丸ごと」ではなくサブワードなのか

単語をそのままトークンにすると、語彙サイズが膨大になり、しかも見たことのない単語(新語、固有名詞、誤字)を扱えない。逆に文字単位にすると語彙は小さく済むが、シーケンスが長くなりすぎて計算コストが跳ね上がる。サブワード方式は「頻出語は丸ごと、珍しい語は分解」という折衷で、語彙サイズと系列長のバランスを取りつつ未知語にも強い、という三方一両得を実現している。

日本語と英語でトークン化を見てみる

「私は猫が好き」
101
55
892
33
好き
415
→ 5トークン
"I like cats"
I
40
like
301
cats
8820
→ 3トークン

長い単語はサブワードに分割される

BPEの面白い性質として、頻繁に登場する単語は1つのトークンに、珍しい単語は複数のサブワードに分割される。たとえば英語の "tokenizing" は、GPT-4のトークナイザーで token / iz / ing の3トークンに分かれる。"ing" は英語で頻出する語尾なので、独立したトークンとして登録されているわけだ。

"tokenizing"
token
11959
iz
337
ing
750
→ 3トークン (cl100k_base / GPT-4)

トークナイザーは世代とモデルで違う

同じ「BPE 系」でも、モデルごとに語彙サイズも分割の仕方も異なる。代表的なものを並べると——

50,257
GPT-2 (BPE)
256バイト + 50,000マージ + 特殊1
~100k
GPT-3.5 / GPT-4 (cl100k_base)
tiktoken
~200k
GPT-4o / o1 (o200k_base)
非英語の効率を改善
128k
Llama 3 (SentencePiece + tiktoken系)
Meta AI

Llama 系は SentencePiece(Kudo & Richardson 2018)という、空白も「ただの文字」として扱える仕組みをベースにしているため、空白の概念が曖昧な日本語などにも素直に適用できる。Claude も独自の BPE 系トークナイザーを使っており、Anthropic 公式の `count_tokens` API でしか正確なトークン数を測れない。

Note

日本語などの非ラテン文字言語は、英語ベースで設計されたトークナイザーで処理すると、英語の同じ意味の文章よりトークン数が多くなる傾向がある。GPT-4o の o200k_base は、cl100k_base に比べて非英語のトークン圧縮率を大きく改善した(言語によっては 1.4〜2 倍以上の効率化)ことが OpenAI の公式発表で報告されている。API のコストは「トークン課金」なので、これは英語以外を扱うユーザーにとって実質的な値下げでもある。

Source: OpenAI tiktoken (github.com/openai/tiktoken) / Sennrich, Haddow & Birch 2016 (arXiv:1508.07909) / Gage 1994, "A New Algorithm for Data Compression"

Section 06

Embedding——数字に意味を持たせる

トークン化しただけでは、ただ文字が数字になっただけ。意味の関係性は表現できていない。そこで、各数字を「ベクトル」(複数の数値の組)に変換する。これがEmbeddingだ。

トークン
数字ID
892
ベクトル(2次元の例)
(0.9, 0.8)

このベクトルが配置される空間では、意味が似ている言葉ほど近い座標に配置される。下の2次元の例で見てみよう。

EMBEDDING SPACE — 2次元の例
x →
↑ y
猫 (0.9, 0.8)
犬 (0.8, 0.7)
象 (0.7, 0.3)
冷蔵庫 (0.1, 0.2)
電子レンジ
近い!
観察 「猫」と「犬」は座標上でも近く、「冷蔵庫」は遠い。意味の近さが、空間上の距離として表現されている

※ 上の図は説明のため2次元にしているが、実際のモデルでは数千次元のベクトルが使われる。代表的なモデルの埋め込み次元は——

768〜1600
GPT-2 (small〜XL)
small=768, medium=1024, large=1280, XL=1600
12,288
GPT-3 (175B)
GPT-2 比でおよそ8倍
4,096
Llama 3 8B
層数32 / ヘッド32

「静的」埋め込みと「文脈付き」埋め込み

埋め込みには大きく 2 つの世代がある。第一世代は静的(static)埋め込み——Mikolov らの Word2Vec(2013)や Pennington らの GloVe(2014, Stanford)が代表で、「bank」という単語には文脈に関わらず常に同じベクトルが割り当てられる。第二世代が文脈付き(contextual)埋め込み。BERT や Transformer 系の隠れ状態は、同じ「bank」でも前後の文脈によって毎回違うベクトルを返す——「川の bank」と「銀行の bank」で表現が変わるわけだ。LLM の各層の出力は、この文脈付き表現を「もっと深く」していく工程と見ることもできる(詳細は次章)。

「近さ」はどう測るか——コサイン類似度

ベクトル空間で「意味が近い」を数値化する標準がコサイン類似度。2 つのベクトルがなす角度の cos を取り、−1(真逆)〜+1(同じ向き)で表す。長さではなく「向き」を見るので、頻出語と希少語のように出現回数(=ベクトルのスケール)が大きく違う単語どうしでも、意味の方向が一致していれば高いスコアになる——これが、ベクトル検索や RAG で使われる類似度の基本だ。

ベクトル化された単語は「計算」ができる

座標上の位置関係で意味を表現できるということは、意味を「計算」できるということでもある。Mikolovらが2013年に発表したword2vecで広く知られるようになった、有名な例:

king man + woman queen

「王様」から「男性」の要素を引き、「女性」を足すと「女王」のベクトルに近い場所にたどり着く。「日本における寿司」と「ドイツにおける何か」を同じ関係性で結べばソーセージが出てくる、といった例もある。座標空間における「方向(差)」が、意味の関係を捉えているのだ。

また、特定の軸(次元)が特定の意味と対応することもある。たとえば数字の「one, two, three, four」を空間にプロットすると、ある軸方向に数字が大きくなる順に並んで配置されるなど、軸そのものが「数字の大きさ」のような意味を担うことがある。

Note

「king − man + woman = queen」は、word2vec/GloVeなどの単語埋め込みで観測される有名な性質。Mikolov et al. (2013) の論文 "Linguistic Regularities in Continuous Space Word Representations" で報告された。なお、厳密には入力された単語(king, man, woman)を結果から除外した上で最近傍を取らないと、計算結果が "king" 自身に最も近くなることが多い、という注意点もある。

また、この類推がいつでも綺麗に成り立つわけではないことも知っておきたい。静的埋め込みでは、学習データの偏りがそのままベクトル空間の方向に焼き付く。Bolukbasi ら(2016)の論文「Man is to Computer Programmer as Woman is to Homemaker?」は、Google News で学習された Word2Vec で「man : computer programmer :: woman : homemaker」という強烈なジェンダーバイアスが現れることを示した——ベクトル演算の優雅さの裏で、データの偏見ごと学習しているわけだ。文脈付き埋め込みやより大きな LLM では類推性能はむしろ評価されにくくなり、「king − man + woman ≈ queen」は「特定の条件下で起こる現象」として捉えるのが現代の理解。

Source: Mikolov et al. 2013 (arXiv:1301.3781) / Pennington et al. 2014 "GloVe" (Stanford NLP) / Bolukbasi et al. 2016 (arXiv:1607.06520)