文字単位言語モデルを200行で理解する:LLMの本質
Karpathyの教育プロジェクトに学ぶ最小実装の意義
Andrej Karpathyは、LLMの仕組みを段階的に実装しながら学ぶ教育シリーズ「Neural Networks: Zero to Hero」を公開しています。このシリーズでは、micrograd(自動微分エンジン)とmakemore(文字単位の名前生成モデル)という2つのプロジェクトを中心に、大規模言語モデルの数学的・アルゴリズム的な基盤を最小限のコードで体感できます。
これらに着想を得た200行程度の純粋なPython実装は、依存ライブラリなしに「トークン化・逆伝播・生成」を一から実装することで、LLMが本質的には確率的な次トークン予測の繰り返しに過ぎないことを明示します。注意すべきは、この実装はSelf-AttentionやMLPブロックを持たないため、GPT-2などの Transformer モデルとは構造が大きく異なります。あくまで文字レベルの統計的言語モデルとして位置づけるのが正確です。
なぜ依存ライブラリを排除したのか?
microgradの設計思想は、依存ライブラリを排除することにあります。これは学習目的の明確な選択です。PyTorchやJAXなどのフレームワークを使わないことで、学習者は「勾配の計算」「パラメータの更新」「損失関数の定義」のすべてを、自らの手で実装する体験を得られます。
自動微分(計算グラフを用いて勾配を自動計算する手法)の仕組みは、Valueクラスという単一のクラスで実装され、forward・backwardの連鎖律が手動で記述されています。この設計は、機械学習の「魔法」が、実は単純な微積分と行列演算の組み合わせであることを明確に示します。
データセット:32,000の名前が学習のすべて
名前データの統計:語彙サイズと平均長
makemoreの学習に用いられるデータセットは、約32,000個の名前から構成され、それぞれが独立した文書として扱われます。このデータセットは、names.txtというファイルから読み込まれ、各行が1つの名前に対応します。データセット全体の語彙サイズは、出現するすべての小文字アルファベット(a-z)の合計26文字に加え、文書の境界を示す特殊トークンBOSを含めた27種類です。
語彙サイズが小さく、平均長が短い構造は、モデルが統計的パターンを学習する際の計算負荷を最小限に抑え、理解の深さを優先する設計哲学を体現しています。学習は、この27種類のトークンの組み合わせから生じる確率分布を捉えることに集中しており、複雑な語彙や構文の処理は一切含まれません。
BOSトークンの役割と文書境界の定義
BOS(Beginning of Sequence)トークンは、各名前文書の開始と終了を明示的に示すための特殊なシンボルです。この設計により、モデルは「ある名前が終わり、新たな名前が開始される」境界を明確に認識できるようになります。BOSトークンは、語彙の最後の位置(インデックス26)に割り当てられ、他の文字トークンとは完全に分離して扱われます。
たとえば、名前「emma」と「olivia」が連続して出現した場合、モデルは[BOS, e, m, m, a, BOS, o, l, i, v, i, a, BOS]という系列として解釈します。このように、BOSは単なる区切り文字ではなく、生成プロセスの開始条件として機能し、推論時に新しい名前の生成をトリガーする役割を果たします。
トークナイザー:文字単位のシンプルな変換
26文字+BOSで構成される27次元の語彙
このモデルのトークナイザーは、文字単位で動作する最もシンプルな設計を採用しています。入力データには約32,000の名前が含まれており、これらから出現する一意な文字を抽出することで、語彙を構築します。具体的には、小文字の英字aからzまでの26文字が検出され、それぞれに整数IDが割り当てられます。さらに、文書の境界を示す特別なトークンであるBOS(Beginning of Sequence)を追加することで、語彙の総数は27となります。この語彙サイズは、$ \text{vocab\_size} = 26 + 1 = 27 $ と数式で表されます。
各トークンは単なる離散的なシンボルに過ぎず、数値自体に意味はなく、たとえば絵文字に置き換えても動作は変わりません。モデルは、この27種類のトークン列を入力として受け取り、次のトークンを確率的に予測します。たとえば、「emma」と「olivia」が連続する場合、系列は$[BOS, e, m, m, a, BOS, o, l, i, v, i, a, BOS]$となり、BOSは名前の開始と終了を明確に区切る役割を果たします。
tiktokenとの比較:効率性と単純さのトレードオフ
実用的な大規模言語モデルでは、tiktoken のようなサブワードトークナイザーが使用され、GPTなどのモデルでは効率性を重視して複数文字の単位を一つのトークンとして処理します。これにより、語彙サイズを数千〜数万に抑え、計算リソースの消費を削減できます。
一方、この最小実装では、あえて文字単位のトークナイザーを採用しており、語彙サイズは27と極めて小さく、実装も10行以内で完了します。この選択は、トレードオフとして「効率性」を犠牲にし、「理解のしやすさ」を優先した結果です。tiktokenは語彙の多様性と圧縮率のバランスを取るために複雑なアルゴリズムを用いますが、この実装のトークナイザーは、単純な文字集合のソートとBOSの追加だけで実現されます。
自動微分:Valueクラスで学ぶ逆伝播
連鎖律と計算グラフの実装:forwardとbackward
microgradにおける自動微分は、Valueクラスを用いて計算グラフを明示的に構築します。このクラスは、値(data)とその勾配(grad)を保持し、演算の入出力関係を隠蔽せずに記録します。たとえば、加算や乗算といった基本演算は、__add__や__mul__メソッド内で新しいValueインスタンスを生成し、その親ノードとして自身と引数を登録します。
この構造により、計算グラフは順伝播(forward)時に動的に構築され、逆伝播(backward)時にはこのグラフを逆向きにたどって連鎖律を適用します。連鎖律は、合成関数の微分を各段階の微分を掛け合わせることで計算する原理であり、たとえば $ z = f(g(x)) $ のとき、$ \frac{dz}{dx} = \frac{dz}{dg} \cdot \frac{dg}{dx} $ という関係が成り立ちます。Valueクラスは、各演算の微分規則(たとえば乗算なら $ \frac{\partial (a \cdot b)}{\partial a} = b $)を内蔵し、backward()メソッド呼び出し時に子ノードに勾配を伝播させます。
勾配の伝播:パラメータ更新の数学的基盤
勾配の伝播は、損失関数から各パラメータへの影響を連鎖律を用いて逐次的に計算するプロセスです。この実装では、損失がスカラー値として定義され、この値を起点としてbackward()が呼び出されます。各Valueノードは、自身の勾配を子ノードから受け取り、さらに親ノードにその影響を逆方向に伝達します。
たとえば、線形層の重み $ w $ と入力 $ x $ の積 $ y = w \cdot x $ が損失 $ L $ に寄与するとき、$ \frac{\partial L}{\partial w} = \frac{\partial L}{\partial y} \cdot x $ と計算されます。最終的に、Adamオプティマイザはこれらの勾配を用いてパラメータ更新を行います。パラメータ更新の数学的基盤は、$ w_{\text{new}} = w_{\text{old}} - \eta \cdot \frac{\partial L}{\partial w} $ という勾配降下の原理に依存しており、ここで $ \eta $ は学習率です。
モデルアーキテクチャ:単純な構造で複雑な予測
埋め込み層と線形出力層の構成
このモデルのアーキテクチャは、埋め込み層(embedding layer)と線形出力層(lm_head)の二段構成で成り立っています。入力となるトークンIDは、$ V \times D $ のパラメータ行列 $ E $ を通して、$ D $ 次元の埋め込みベクトルに変換されます。ここで $ V $ は語彙サイズ(27)、$ D $ は埋め込み次元数です。
その後、このベクトルは $ D \times V $ の重み行列 $ W $ とバイアス $ b $ を用いて線形変換され、出力層(lm_head)から各トークンの対数確率(ロジット)が生成されます。この構造は、$ \mathbf{y} = \text{lm\_head}(E[\mathbf{x}]) = E[\mathbf{x}] \cdot W + b $ という単純な行列演算で実現されています。パラメータ数は、埋め込み層で $ 27 \times D $、lm_headで $ D \times 27 $ の合計 $ 2 \times 27 \times D $ となり、非常にコンパクトです。
Self-AttentionとMLPの省略とその意味
この最小実装は、Transformerアーキテクチャの中核である自己注意機構(Self-Attention)と多層パーセプトロン(MLP)を持ちません。そのため、GPT-2のようなTransformerモデルとは構造が根本的に異なります。正確には、これは文字単位の統計的言語モデルであり、各トークンの予測が直前までの文字列のパターンに基づく bigram/n-gram 的な動作をします。
Self-Attentionは、トークン間の長距離依存関係をモデル化するための強力な機構です。名前のような短いシーケンスでは長距離依存が不要なため、この省略で十分な生成が可能ですが、この点を「GPTと同じ本質」と混同しないことが重要です。Self-AttentionとMLPの省略により、計算グラフが極めて扁平化され、勾配計算の連鎖律が直感的に理解できる設計になっています。
学習ループ:Adamオプティマイザでパラメータを最適化
クロスエントロピー損失の計算とその性質
学習ループでは、モデルの予測と正解ラベルの差異を測るための損失関数として、クロスエントロピー損失(cross-entropy loss)が用いられます。この損失関数は、ソフトマックス関数(softmax)を経て得られた予測確率分布と、実際のトークン分布との間の不一致を定量的に評価します。
具体的には、ある時刻 $ t $ における真のラベル $ y_t $ と、モデルが出力する確率分布 $ \hat{p}_t $ に対して、損失は以下のように定義されます:
$$ \mathcal{L} = -\log(\hat{p}_t[y_t]) $$ここで、$ \hat{p}_t[y_t] $ は、正解トークンに対応する予測確率です。正解の確率が高ければ損失が小さくなり、逆に低ければ損失が急激に増加する性質により、モデルは正解に近づくように勾配が誘導されます。
1エポックあたりのパラメータ更新回数と収束速度
学習ループでは、約32,000個の名前データに対してパラメータ更新が繰り返されます。Adamオプティマイザ(勾配の移動平均と二乗移動平均を用いて学習率を個別に調整する最適化手法)は、勾配の一次モーメント(平均)と二次モーメント(分散)を逐次推定し、学習率をパラメータごとに適応的に調整します。この機構により、各次元の勾配のスケールの違いを吸収し、安定した収束を実現します。
推論:1文字ずつ未来を予測する生成プロセス
温度パラメータが生成結果に与える影響
推論段階では、モデルが次の文字の確率分布を出力し、その中から実際のトークンをサンプリングします。このとき、温度パラメータ(temperature)は確率分布の形状を制御する重要なハイパーパラメータです。温度が $ T = 1 $ のとき、モデルは学習時に得た確率分布をそのまま用います。温度を $ T < 1 $ に設定すると、確率分布は尖り、高確率の文字がより強く選ばれます。逆に、$ T > 1 $ とすると、分布は平滑化され、低確率の文字も比較的容易に選択されます。
このプロセスは、ロジット $ z $(確率分布の対数に相当する生の値)を次のように変換することで実現されます:
$$ p'_i = \frac{\exp(z_i / T)}{\sum_j \exp(z_j / T)} $$たとえば、$ T = 0.5 $ では一貫性の高い名前が生成されやすく、$ T = 1.5 $ では予期せぬ組み合わせが現れる傾向があります。
BOSから始まる逐次生成のメカニズム
推論ループは、BOS(Beginning of Sequence)トークンから始まり、1文字ずつ未来を予測する逐次生成を繰り返す構造を持っています。初期状態では、入力シーケンスにBOSトークンのみを含み、モデルはその状態から次の文字の確率分布を計算します。得られた分布からサンプリングにより次の文字 $ x_1 $ を選択し、これを入力に加えて再び推論を実行します。
このプロセスは、$ x_1, x_2, \dots, x_t $ と逐次的に拡張され、次の文字の予測は過去のすべての文字に依存します。生成は、BOSトークンが再び出現するまで、または最大長に達するまで継続されます。埋め込み層と線形出力層の直列計算によって高速に処理され、実行効率の高さと理論的透明性の両立を実現しています。
よくある質問
Q: この実装はChatGPTと同じ仕組みですか?
基本的なアイデア(トークン化・確率的次トークン予測・勾配降下)は共通していますが、構造は大きく異なります。ChatGPTはSelf-AttentionとMLPブロックを持つTransformerアーキテクチャで動作しますが、この実装にはそれらがありません。「LLMの本質的な学習ループを体感する」という教育目的には最適ですが、「同じ仕組み」と言うのは正確ではありません。
Q: この実装で日本語を学習させるとどうなりますか?
日本語を学習させると、語彙サイズが急激に増加します。英語は26文字+記号で済みますが、日本語は漢字・ひらがな・カタカナが数万種類存在するため、vocab_sizeが数千〜数万に膨らみ、学習が極端に遅くなります。これは文字単位トークナイザーの限界を示す重要な実験です。
Q: 200行の実装は、実用的なAIモデルの開発に役立ちますか?
直接的な実用性は低いですが、教育的価値は極めて高いです。LLMの内部構造を「ゼロから理解」するための最短ルートであり、PyTorchやJAXの抽象化の背後にある数学的原理を体感できます。実務ではこの理解が、モデルのチューニングやデバッグに直結します。
Q: GPT-4との性能差はどのくらいですか?
この実装は数万の名前を学習するだけなので、大規模モデルと比べると性能は極めて低いです。GPT-4のパラメータ数はOpenAIが公式に公表していませんが、数千億〜兆単位とされており、数兆トークンの学習データで複雑な文脈理解や論理的推論が可能です。一方、この実装は単に文字の統計的パターンを模倣するだけです。比較よりも「本質を理解するための出発点」として捉えるのが適切です。
Q: 動かすにはどのくらいの計算リソースが必要ですか?
CPUだけで十分です。GPUは不要です。学習データは約32,000個の名前で、パラメータ数は数千個と極めて小さいため、一般的なPCで数分以内に学習が完了します。高価なハードウェアなしでも、LLMの本質を体感できる点がこの実装の大きな魅力です。