文字単位言語モデルを200行で理解する:LLMの本質
文字単位言語モデルを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()メソッド呼び出し時に子ノードに勾配を伝播させます。 ...