Global Trend Radar
Web: zenn.dev US web_search 2026-05-01 09:39

AIエージェントもCoTもマルチモーダルも、結局「次トークン予測」の応用だった

元記事を開く →

分析結果

カテゴリ
AI
重要度
72
トレンドスコア
36
要約
AIエージェントもCoTもマルチモーダルも、結局「次トークン予測」の応用だった こたこた博 transformer ChatGPT llm 生成 AI マルチモーダル idea ChatGPTは「考えて」いないし、あなたの質問を「理解」もしていません。 え?そうなの? それでもコードを書いて、設計レビューまでしてくれますよ? 一体どうなっているのでしょうか。 仕様書も読んでいないはずなのに、仕様を要約してくる さっきまでよく知らなかった
キーワード
AIエージェントもCoTもマルチモーダルも、結局「次トークン予測」の応用だった こたこた博 transformer ChatGPT llm 生成 AI マルチモーダル idea ChatGPTは「考えて」いないし、あなたの質問を「理解」もしていません。 え?そうなの? それでもコードを書いて、設計レビューまでしてくれますよ? 一体どうなっているのでしょうか。 仕様書も読んでいないはずなのに、仕様を要約してくる さっきまでよく知らなかった技術スタックについて、最初から詳しかったような顔で語り始める なのに、変なところで堂々と嘘をついたりもする この「賢いようで、よくわからない感じ」が、LLM(大規模言語モデル)の 気持ち悪さ だと思います。 この記事では、このモヤモヤを少しでも減らすために、 LLMの本質を「次トークン予測マシン」として捉え直し、 マルチモーダル/エージェント/Chain-of-Thought(思考の連鎖)も すべて同じ原理の上に乗っていること を、疑似コードベースで整理してみます。 「コードとして見てなんとなく腑に落ちる」レベルの深さを目指します。 そのまま動くコードではありませんので、そこはご注意ください。 1. エンジニアから見るLLMの「気持ち悪さ」 まずは、LLMに対するエンジニアの典型的なモヤモヤを挙げてみます。 仕様書がないのに動いている テストがしにくい(同じプロンプトでも毎回出力が揺れる) ソースコードも読めない、デバッガも役に立たない なのに、かなりの精度で設計レビューやコード補完をしてくる これ、普通のソフトウェアコンポーネントとして考えると、かなり異常ですよね。 「どういう入力に対して、どういう出力が返るのか」 を仕様レベルで説明してくれないコンポーネントが、システムの中枢にいる。 気持ち悪いし怖いです。 この「ブラックボックス感」を少しでも弱めるために、 まずは「LLMの核って、結局はこれだけだよね」というところまで分解してみます。 2. 核心:LLMは「次のトークンを1個ずつ予測して選んでいるだけ」 いきなりですが、まずは 極限までざっくり化した疑似コード を見てみましょう。 def generate_text (prompt_tokens, max_new_tokens = 128 ): tokens = prompt_tokens[:] for _ in range (max_new_tokens): logits = transformer_forward(tokens) # 現在のトークン列をモデルに入力し、全位置のスコアを計算する probs = softmax(logits[ - 1 ]) # 最後のトークンの確率分布を取得する next_token = sample_from(probs) # 確率分布に従って、次の1トークンを決める:greedy / sampling など tokens.append(next_token) # 選ばれたトークンを現在のトークン列の末尾に追加 if next_token == EOS_TOKEN : break return tokens やっていることを日本語にすると: いままでのトークン列(文章の先頭からここまで)を全部突っ込む 「次の位置にどのトークンが来そうか」の確率分布を出す 1トークン選ぶ それを文末に付けて、また 1 に戻る これをひたすら繰り返しているだけ です。 「そんな単純なわけないだろ」と思うかもしれませんが、疑似コードレベルで書くと本当にこの程度の処理なんです。 では、この「次トークン予測マシン」がどう学習されているのか、もう一段、深堀りします。 3. 「桃太郎」で見る学習:次の単語を当て続ける 学習時にやっていることも、本質はシンプルです。 例えば、「桃太郎」の冒頭だけを考えます。 昔々、あるところにおじいさんとおばあさんが住んでいました。 これを、トークン列だと思ってください(空白や句読点などの細かい扱いは一旦目をつぶって)。 学習時には、こんな問題をひたすら解かせています。 入力: ["昔々", "あるところに"] 正解: "おじいさん" 入力: ["昔々", "あるところに", "おじいさん"] 正解: "と" 入力: ["昔々", "あるところに", "おじいさん", "と"] 正解: "おばあさん" これをコードっぽく書くと、こんなイメージです。 def training_step (model, context_tokens, target_token): logits = transformer_forward(context_tokens) # 全ての位置のスコアを計算 pred_logits = logits[ - 1 ] # 最後のトークンの予測結果を取り出す loss = cross_entropy(pred_logits, target_token) # 正解との誤差を算出 loss.backward() # モデル内の各パラメータの勾配を逆算 optimizer.step() # モデルの重みパラメータを更新 context_tokens :いままでの文章 target_token :次に来てほしい単語(正解) cross_entropy :正解トークンの確率が高くなるようにする損失関数 これを Web全体レベルのコーパスで、ひたすら繰り返している のが事前学習です。 最初はランダムな予想しかできませんが、 何億、何兆という回数の「次トークン当てクイズ」を繰り返すうちに、 「この文脈なら、次はだいたいこの単語が来るよね」 という「勘」が異常なほど鋭くなっていきます。 ここまでのまとめ: LLMの本質は「次トークン予測ループ」 学習も、ひたすら「次トークン当ての誤差を減らす」ようにがんばる さて、これだけ聞くと「そんなものが、なんであんなに賢いんだ」と思いますよね。 そのギャップを埋めるために、内部構造を処理ブロックレベルで押さえておきます。 4. 内部構造:トークナイザ/埋め込み/Transformerをざっくり見る LLMの内部をざっくり処理ブロックレベルで分解します。 大まかには、こんな流れです。 文字列 → トークン列(ID列) トークン列 → ベクトル列(埋め込み+位置情報) ベクトル列 → Transformerブロックを N 層通す 最後の位置のベクトル → logits(各トークンのスコア) 4-1. トークン化 tokens = tokenizer.encode( "今日はいい天気ですね" ) # 文字列をID列にエンコード # 例: [1012, 42, 88, 9001, 5, 999] 4-2. 埋め込み def embed (tokens): token_vecs = token_embedding[tokens] # 各トークンを多次元の数値ベクトルに変換 pos_vecs = positional_embedding[: len (tokens)] # 文章内での「位置」を表すベクトルを生成 return token_vecs + pos_vecs token_embedding :ID → ベクトル の表(行列) positional_embedding :位置ごとのベクトル(語順情報) 4-3. Transformerブロック(かなり抽象化) Self-Attention、Feed Forwardは一旦ブラックボックスにしておきます。 def transformer_block (x): x = x + self_attention(norm(x)) # どの単語が重要かを判断し、文脈情報を強化 x = x + feed_forward(norm(x)) # Attentionで集めた情報を整理・加工し、より高度な表現に変換 return x 4-4. 次のトークン予測の処理全体 def transformer_forward (tokens): x = embed(tokens) # 入力されたトークン列を、位置情報込みのベクトルに変換 for block in transformer_blocks: # Transformerブロックを N 層通す(例えば 24 層とか 48 層とか) x = transformer_block(x) logits = final_linear(x) # 最終的なベクトルを、全語彙に対応するスコア(logits)に変換 return logits この処理ブロックが、「 文脈を読んで、各位置の意味ベクトルを更新する関数 」だと思ってもらえれば十分です。 ここまでざっくりと見通せたところで、いよいよ「拡張」の話に進みます。 5. 拡張①:マルチモーダル ― 画像もトークンに落とせば同じ土俵 ここからが面白い部分です。 テキスト専用だったはずのLLMが、 なぜ画像や音声まで扱えるようになったのか? という話を、あくまで「次トークン予測」の延長として見ていきます。 5-1. 画像 → 離散トークン ざっくりいうと、画像も 小さなパッチ(例えば 16×16)に分割し、 それぞれを特徴ベクトルに変換し、 それを「似たものを同じIDにする」感じで離散化する ことで、「画像トークンID列」に変換できます。 疑似コードでいうと: def image_to_tokens (image): patches = split_into_patches(image) # パッチに分割 feats = vision_encoder(patches) # 特徴ベクトルの列に変換 img_tokens = quantize(feats) # 離散化したIDにマッピング return img_tokens vision_encoder は CNN や ViT など、実装はいろいろですが、やっていることは「画像を特徴ベクトルの列に変換する」ことです。 5-2. テキストと画像を同じシーケンスに流し込む あとは、テキストトークンと画像トークンを 同じシーケンスに連結 してしまえばOKです。 def multimodal_qa (question_text, image): text_tokens = tokenizer.encode(question_text) img_tokens = image_to_tokens(image) seq = [ BOS ] + text_tokens + [ IMG_TOKEN ] + img_tokens # テキストと画像を「1本のシーケンス」に連結 answer_tokens = generate_text(seq, max_new_tokens = 128 ) # 続きのテキスト(回答)を生成 return tokenizer.decode(answer_tokens) # 生成されたトークン列を人が読めるテキスト(文字列)に戻す ここでも重要なのは、 モデル側から見ると、「ただトークン列が長くなっただけ」 画像だからといって特別なことはしていない ひたすら 「次のトークンIDはどれか」 を予測しているだけ という点です。 6. 拡張②:エージェント ― ツール呼び出しもテキストとして吐いているだけ 次の拡張は「エージェント」です。 文章を返すだけのモデルのはずが、 なぜフライト予約やコード実行までできるのか? 答えを先に言うと、これもやはり「テキスト」です。 6-1. LLMから見れば、JSONもただの文字列 最近のエージェント系では、LLMにこのようなフォーマットの出力をさせます。 { "tool" : "web_search" , "args" : { "query" : "大阪 出張 フライト 3月15日" } } LLM側から見れば、これは「 { とか "tool" とかのトークン列」にすぎません。 「関数を呼んでいる」わけではありません。 単に 「関数を呼んでくれ」と書かれた文字列 を出力しているだけです。 6-2. 実際に動かしているのは外側のループ 実際にツールを呼び出しているのは、LLMの外側にいる処理です。 def agent (user_input): messages = [{ "role" : "user" , "content" : user_input}] while True : llm_output = call_llm(messages) if is_tool_call(llm_output): # ツール呼び出し指示の有無チェック tool_name, args = parse_tool_call(llm_output) # 呼び出し指示JSONデータを解釈 result = execute_tool(tool_name, args) # ここで外部APIなどを実行 messages.append({ "role" : "tool" , "name" : tool_name, "content" : result, }) # ツール処理結果をプロンプト文字列に追加 else : # ツール呼び出し指示が無くなった return llm_output # 最終応答を返す call_llm :いつもの ChatCompletion 的な呼び出し is_tool_call :LLMの出力がJSONっぽいかどうかを判定 execute_tool :HTTPクライアントやDBクライアントなど、普通のコード 「エージェントすごい!」と言われることが多いですが、 魔法の正体は「LLMがJSONっぽい文字列を吐き、周りがそれを実行しているだけ 」です。 やっぱり、LLM自身は 次のトークンを予測してテキストを生成しているだけ なんですね。 7. 拡張③:思考の連鎖 ― 「考え方」もテキストとして書かせる 最後の拡張は、 Chain-of-Thought(CoT) 、思考の連鎖です。 次トークン予測マシンに、どうやって「考えさせる」ことができるのか? 7-1. 普通の回答とCoT回答 普通の問い: Q. ある商品を1個1000円で仕入れて、20%の利益を乗せて売りました。販売価格はいくらですか? 通常のプロンプト: answer = llm( "Q: ... 販売価格はいくらですか?" ) # -> "1200円です。" CoT付きプロンプト: reasoning = llm( "Q: ... 販売価格はいくらですか? \n Let's think step by step." ) # -> "仕入れ値1000円の20%は200円なので、1000+200=1200円です。" 7-2. 二段階プロンプトにしてみる CoTをもう少し一般的に書くと、こんな感じに分解できます。 def cot_answer (question): # 1. まず「思考過程(中間ステップ)」を生成 reasoning = llm(question + " \n Let's think step by step." ) # 2. その思考を要約して、最終回答だけを出させる final = llm( "以下の推論を一文で答えだけ述べてください: \n " + reasoning) return final ポイントは、 「思考過程」もテキストで生成される=トークン列 それを一度外に出してから、もう一回LLMに読ませて処理させる 実質的に計算ステップを増やしている という点です。 人間が紙に途中式を書きながら考えるのと、かなり構造が似ています。 結局ここでも、「思考」そのものが中間トークン列として扱われているだけなんですね。 8. 創発と限界:単純なルール×スケールの先に見えるもの ここまでで見てきた通り、 事前学習:ひたすら次トークン予測 マルチモーダル:画像や音声もトークン列にする エージェント:ツール呼び出し命令もトークン列にする CoT:思考過程もトークン列にする というように、 全部「トークン列」と「次トークン予測」の話 としてまとめることができます。 それでも、ある規模を超えたLLMが、 簡単な推論問題を解けるようになったり 翻訳性能がいきなり良くなったり 設計レビューやコード修正案まで出してきたり するのは、ある種の「創発的な振る舞い」と言えます。 これについては、まだ理論的な理解が完全ではありませんが、 ルールはシンプルでも、スケールさせると質が変わる という現象として捉えておくと、何が起こっているのかを理解できると思います。 一方で、ハルシネーションのような「自信満々の誤答」も、 次トークン予測は、出力内容の真偽は問題にしていない 「文として自然かどうか」 を優先する次トークン予測の結果 として説明できます。 9. まとめ:LLMを「設計可能なコンポーネント」として見る 最後に、この記事で「わかった」「あぁそうか」と思ってもらいたいポイントを箇条書きにします。 LLMの核は「次トークン予測ループ」であり、疑似コードで書くとかなり単純 マルチモーダル/エージェント/CoTは、 何をトークンとして扱うか それをどのループで使うか を変えているだけだが、それによって知能的な能力を高めている 「LLMが何をしているのか」を今回のような抽象度でつかんでおくと、 どこから外部システム(ツール・DB・ルールベース)に任せるか どこまでLLMに自由にさせるか を設計しやすくなる 得体の知れない魔法っぽかったものが、「癖は強いけど設計可能なコンポーネント」に見えてきたでしょうか? 皆さんのプロジェクトでLLMを組み込むときには、 「ここはLLMで良さそう」 「ここは絶対に外側で検証しよう」 「ここはルールベースでガチガチに決めておくべき」 という線引きを、LLM内の処理をイメージしながら考えてみてください。 それが、AI時代、LLM時代のアーキテクト/エンジニアにとって、大事で有用なスキルになると思います。 Appendix:次トークン予測だけで、なぜ「質問」に「回答」できるのか? 文章の続きを予想する「だけ」のAIに、試しに質問をしてみます。すると ― 不思議なことに、ちゃんと答えが返ってきます。 言葉で質問して言葉で答えてくれるから、まるで会話をしているようですよね。 しかし、AIは「文章の続きを予想しているだけ」です。質問の内容を理解して、どう答えるかを考えているわけではありません。 それなのに、なぜ「質問に答えている」ことになるのでしょうか? 答えは「学習した文章の構造」にあります。 AIが学習した文章の中には、質問と答えが書かれた文章が大量に含まれています。 Q&A集のようなページ、企業のFAQページ、疑問を質問して回答してもらえる掲示板などでは、最初に質問が書いてあって、その続きに回答が書かれています。 つまり、 質問の文章の続き を 予測 すると、それは結果的に 回答を予測 していることになる、ということです。 キツネにつままれたような感じかもしれませんが、単純なルールから知能っぽさが見えてくるのは面白いですね。 こたこた博 ITエンジニアです。わりと昔からやってます。新しいこと好きです。 Discussion こたこた博 ITエンジニアです。わりと昔からやってます。新しいこと好きです。 目次 1. エンジニアから見るLLMの「気持ち悪さ」 2. 核心:LLMは「次のトークンを1個ずつ予測して選んでいるだけ」 3. 「桃太郎」で見る学習:次の単語を当て続ける 4. 内部構造:トークナイザ/埋め込み/Transformerをざっくり見る 4-1. トークン化 4-2. 埋め込み 4-3. Transformerブロック(かなり抽象化) 4-4. 次のトークン予測の処理全体 5. 拡張①:マルチモーダル ― 画像もトークンに落とせば同じ土俵 5-1. 画像 → 離散トークン 5-2. テキストと画像を同じシーケンスに流し込む 6. 拡張②:エージェント ― ツール呼び出しもテキストとして吐いているだけ 6-1. LLMから見れば、JSONもただの文字列 6-2. 実際に動かしているのは外側のループ 7. 拡張③:思考の連鎖 ― 「考え方」もテキストとして書かせる 7-1. 普通の回答とCoT回答 7-2. 二段階プロンプトにしてみる 8. 創発と限界:単純なルール×スケールの先に見えるもの 9. まとめ:LLMを「設計可能なコンポーネント」として見る Appendix:次トークン予測だけで、なぜ「質問」に「回答」できるのか?

類似記事(ベクトル近傍)