🐕‍🦺

【LLM基礎#1】Transformerとトークナイゼーションの仕組みを理解する

に公開

はじめに

ChatGPTやClaudeなどのLLM(大規模言語モデル)が業務で使われる場面が当たり前に増えてきましたが、モデルの仕組みや限界を知らずに使っていると、期待した出力ができないことがある。

今回はまずその中でも特に基盤の基盤である「Transformer」と「トークナイゼーション」について、できるだけシンプルに仕組みを理解する。
この2つの仕組みを知って、モデルがどういう前提で動いてるかをわかるようになるのがゴール。
APIを使うときにエラーやコストを事前に防ぐヒントになればと最終的には思う。
コードもちょっと動かしてみながら、なんでこうなってるのを自分なりに整理する。

1. Transformerって何?

なぜTransformerが出てきたのか

以前はRNNやLSTMといったモデルで、文章を1語ずつ順番に処理していました。しかし、長文になると前の情報が忘れられたり、処理が遅くなる問題がありました。

Transformerはこの課題を解決するために登場しました。「全単語を一気に見渡して処理しよう」という発想でGoogleの研究者が作った機械学習モデルです。

Self-Attentionの仕組み

Transformerのコアな仕組みはSelf-Attentionです。これは、各単語が他の単語にどれだけ注目すべきかをスコアで決める仕組みです。

  • Query(Q): 何を探したいか
  • Key(K): 各単語の特徴
  • Value(V): 実際の情報

QとKを掛け合わせて重み(スコア)を出し、それをVに掛けて全体をまとめる。

import numpy as np

# 入力(3単語、埋め込み次元4)
x = np.array([
    [1.0, 0.0, 1.0, 0.0],  # 単語1
    [0.0, 2.0, 0.0, 2.0],  # 単語2
    [1.0, 1.0, 1.0, 1.0],  # 単語3
])

# 重み行列(シンプルな線形変換)
W_q = np.random.rand(4, 4)  # Query
W_k = np.random.rand(4, 4)  # Key
W_v = np.random.rand(4, 4)  # Value

# Q, K, Vを計算
Q = x @ W_q
K = x @ W_k
V = x @ W_v

# Attentionスコア(QとKの内積をソフトマックス)
scores = Q @ K.T
weights = np.exp(scores) / np.exp(scores).sum(axis=-1, keepdims=True)

# 重み付き平均
attention_output = weights @ V

print("Attention出力:")
print(attention_output)
#Attention出力:
[[1.234 0.845 1.423 0.987]
 [1.102 1.210 1.532 0.654]
 [1.189 1.156 1.601 0.872]]

この出力は、各単語が文の中でどういう意味になるかを、周りの単語も見ながら計算し直したもの。たとえば1行目は、1単語目がどんな意味を持つかを数値で表してる。
この数値は、QとKで出した「注目すべき度合い(重み)」を使って、Vの情報を足し合わせて作ったもので、勘違いとしてどの単語をどれだけ重視したか(=重み)そのものではなく、重みを使って作られた新しい情報という立ち位置。値が1.5だからといって「重視度が高い」という意味ではないことに注意。

Positional Encodingとは?

TransformerにはRNNのような「順番の記憶」がないので、位置情報をあらかじめ数値(ベクトル)で付け足す。これをPositional Encodingと呼ぶ。

x_with_pos = word_embeddings + positional_encodings

2. トークナイゼーションって何?

モデルは文字列をそのまま扱えないのでトークンという単位に変換して処理する。

例えば文章は以下のように分解される:

["それ", "は", "バグ", "じゃ", "なく", "て", "仕様", "です"]

tiktoken(トークン数を確認したいときとかに使うopenaiのライブラリ)で確認してみる

import tiktoken
enc = tiktoken.get_encoding("cl100k_base")
tokens = enc.encode("それはバグじゃなくて仕様です")
print(tokens)  # 出力例: [45752, 1456, 5467, 265, 1519, 485, 9284, 1217]
print(len(tokens))  # トークン数: 8

なぜ「それ」が45752なのか?
→ 各トークンにはOpenAIはByte Pair Encoding(BPE)のアルゴリズムを採用してそれに定めた辞書の中で数値IDが割り当てられて、モデル内部ではこの数値で処理される。

コンテキスト長とトークン数

モデルには「一度に読めるトークン数」に制限がある。

  • GPT-3.5: 約4,096トークン
  • GPT-4系: 約128,000トークン

入力・補助情報・出力すべてを含んだ合計がこの上限に収まらないと、出力が途中で切れたり、エラーになる。

3. やってみて思ったこと

  • Transformerって一個一個見ればけっこうシンプル
  • トークンと文字数は全然違う → ちゃんと実験して体感すべき
  • トークナイゼーションの違いでトークン数が大きく変わることがある → APIのコスト試算や上限チェックに影響するので注意が必要
  • 「応答が途中で切れる」「後半が出てこない」といった現象の背景にコンテキスト長の制限がある → 事前にトークン数を把握しておけば入力制限として防げることもある
  • APIで高コストやエラーを防ぐには、トークン数と出力長を事前に管理して見積もっておくと良い → 入力が多い場合はトークン削減の工夫が必要

4. 次にやってみたいこと

  • GPT系 / Claude / Mistral / GeminiなどのLLMごとの違いを比べてみる
  • それぞれのモデルで「速さ」「使えるトークン数」「トークナイゼーションの違い」なども比較してみたい

Discussion