Chain-of-Thought推論を理解してLLMの性能を引き出す

大規模言語モデル(LLM)は複雑な推論タスクが苦手とされてきましたが、Chain-of-Thought(CoT)プロンプティングにより、その能力を大幅に向上させることができます。

本記事では、LLMに段階的な思考プロセスを生成させるCoT推論の仕組みと実装方法を解説します。

本記事の内容

  • Chain-of-Thoughtの基本概念
  • Few-shot CoTとZero-shot CoT
  • 数学的な分析と効果の検証
  • Pythonでの実装例

前提知識

この記事を読む前に、以下の概念を理解しておくと役立ちます。

  • Transformerアーキテクチャの基礎
  • プロンプトエンジニアリングの基本
  • LLMの推論の仕組み

LLMの推論における課題

直接回答の限界

従来のプロンプティングでは、LLMは質問に対して直接答えを出力します。

Q: ロジャーは5つのテニスボールを持っています。テニスボールの缶を2つ買いました。
   各缶には3つのボールが入っています。彼は今いくつのボールを持っていますか?
A: 11

単純な問題では機能しますが、複雑な推論を要する問題では誤答が増えます。

なぜ複雑な推論が難しいのか

Transformerの性質上、各出力トークンは固定された計算量(レイヤー数)で生成されます。複雑な推論には、より多くの「計算ステップ」が必要です。

$$ \text{Complexity}(\text{Answer}) > \text{Fixed Computation per Token} $$

Chain-of-Thoughtの基本概念

アイデア

Chain-of-Thought(CoT)は、最終回答に至る中間的な推論ステップを明示的に生成させる手法です。

Q: ロジャーは5つのテニスボールを持っています。テニスボールの缶を2つ買いました。
   各缶には3つのボールが入っています。彼は今いくつのボールを持っていますか?
A: ロジャーは最初に5つのボールを持っていました。
   2缶買い、各缶に3つのボールがあるので、2×3=6つのボールを得ました。
   合計は5+6=11です。
   答えは11です。

なぜ効果があるのか

  1. 計算のオフロード: 中間ステップを生成することで、実質的な計算量が増加
  2. エラーの局所化: 各ステップでのエラーを特定しやすい
  3. 学習済み知識の活用: 推論パターンは事前学習で獲得済み

$$ P(\text{Answer}|x) = \sum_{\text{CoT}} P(\text{Answer}|\text{CoT}, x) \cdot P(\text{CoT}|x) $$

Few-shot Chain-of-Thought

アルゴリズム

Few-shot CoTでは、推論ステップを含む例をプロンプトに含めます。

Q: カフェに23人の客がいます。17人が帰り、新たに6人が来ました。カフェには何人いますか?
A: 最初に23人いました。17人帰ったので、23-17=6人残りました。
   6人来たので、6+6=12人になりました。答えは12です。

Q: プレイグラウンドに15人の子どもがいます。8人が帰り、3人が来ました。何人いますか?
A: 最初に15人いました。8人帰ったので、15-8=7人残りました。
   3人来たので、7+3=10人になりました。答えは10です。

Q: [新しい問題]
A:

数学的定式化

Few-shot CoTは、条件付き確率の連鎖則として表現できます。

推論チェーン $c = (c_1, c_2, \ldots, c_n)$ と最終回答 $a$ に対して:

$$ P(a, c | x, \text{examples}) = \prod_{i=1}^{n} P(c_i | c_{

Pythonでの実装

class FewShotCoT:
    """Few-shot Chain-of-Thought"""

    def __init__(self, llm_client):
        """
        Args:
            llm_client: LLM APIクライアント
        """
        self.llm_client = llm_client
        self.examples = []

    def add_example(self, question, reasoning, answer):
        """例を追加"""
        self.examples.append({
            'question': question,
            'reasoning': reasoning,
            'answer': answer
        })

    def format_example(self, example):
        """例をフォーマット"""
        return f"""Q: {example['question']}
A: {example['reasoning']}
したがって、答えは{example['answer']}です。"""

    def build_prompt(self, question):
        """プロンプトを構築"""
        examples_text = "\n\n".join([
            self.format_example(ex) for ex in self.examples
        ])

        prompt = f"""{examples_text}

Q: {question}
A:"""
        return prompt

    def solve(self, question):
        """問題を解く"""
        prompt = self.build_prompt(question)
        response = self.llm_client.generate(prompt)
        return response

# 使用例
cot = FewShotCoT(llm_client)

# 例を追加
cot.add_example(
    question="カフェに23人の客がいます。17人が帰り、新たに6人が来ました。カフェには何人いますか?",
    reasoning="最初に23人いました。17人帰ったので、23-17=6人残りました。6人来たので、6+6=12人になりました。",
    answer="12"
)

cot.add_example(
    question="プレイグラウンドに15人の子どもがいます。8人が帰り、3人が来ました。何人いますか?",
    reasoning="最初に15人いました。8人帰ったので、15-8=7人残りました。3人来たので、7+3=10人になりました。",
    answer="10"
)

# 新しい問題を解く
answer = cot.solve("教室に30人の生徒がいます。12人が帰り、5人が来ました。何人いますか?")
print(answer)

Zero-shot Chain-of-Thought

アイデア

Zero-shot CoTは、例を使わずに「Let’s think step by step」のようなトリガーフレーズだけでCoT推論を誘発します。

Q: ジョンは3つのリンゴを持っています。メアリーはジョンの2倍持っています。
   2人合わせていくつのリンゴを持っていますか?

A: ステップバイステップで考えましょう。
   1. ジョンは3つのリンゴを持っています。
   2. メアリーはジョンの2倍、つまり3×2=6つ持っています。
   3. 合計は3+6=9つです。
   答えは9です。

効果的なトリガーフレーズ

フレーズ 効果
Let’s think step by step 最も効果的
Let’s work this out in a step by step way 効果的
First, 推論の開始を促す
Let’s break this down 分解を促す

Pythonでの実装

class ZeroShotCoT:
    """Zero-shot Chain-of-Thought"""

    def __init__(self, llm_client):
        self.llm_client = llm_client
        self.trigger_phrase = "ステップバイステップで考えましょう。"

    def solve(self, question, extract_answer=True):
        """
        問題を解く

        Args:
            question: 質問
            extract_answer: 最終回答を抽出するか

        Returns:
            reasoning: 推論プロセス
            answer: 最終回答(extract_answer=Trueの場合)
        """
        # Step 1: 推論を生成
        reasoning_prompt = f"""Q: {question}

A: {self.trigger_phrase}"""

        reasoning = self.llm_client.generate(reasoning_prompt)

        if not extract_answer:
            return reasoning

        # Step 2: 回答を抽出
        extraction_prompt = f"""Q: {question}

A: {self.trigger_phrase}
{reasoning}

したがって、答えは(数字のみで答えてください):"""

        answer = self.llm_client.generate(extraction_prompt)

        return reasoning, answer

# 使用例
zero_shot_cot = ZeroShotCoT(llm_client)

question = "店に50個のリンゴがあります。30個売れ、20個入荷しました。いくつありますか?"
reasoning, answer = zero_shot_cot.solve(question)

print("推論プロセス:")
print(reasoning)
print(f"\n答え: {answer}")

Self-Consistency

アルゴリズム

Self-Consistencyは、複数の推論パスを生成し、多数決で最終回答を決定する手法です。

$$ \text{Answer}^* = \arg\max_a \sum_{i=1}^{n} \mathbb{1}[\text{Answer}_i = a] $$

数学的直観

複数の独立した推論パスが同じ答えに到達する場合、その答えの信頼性は高いと考えられます。

$$ P(\text{Answer correct}) \approx 1 – (1 – p)^n $$

ここで $p$ は単一パスでの正答確率、$n$ はパス数です。

Pythonでの実装

import numpy as np
from collections import Counter

class SelfConsistencyCoT:
    """Self-Consistency with Chain-of-Thought"""

    def __init__(self, llm_client, n_paths=5, temperature=0.7):
        """
        Args:
            llm_client: LLM APIクライアント
            n_paths: 生成する推論パスの数
            temperature: サンプリング温度
        """
        self.llm_client = llm_client
        self.n_paths = n_paths
        self.temperature = temperature

    def extract_answer(self, reasoning):
        """推論から数値回答を抽出"""
        # 簡易的な数値抽出(実際にはより堅牢な方法が必要)
        import re
        numbers = re.findall(r'\d+', reasoning)
        if numbers:
            return numbers[-1]  # 最後の数値を回答とする
        return None

    def solve(self, question, examples=None):
        """
        Self-Consistencyで問題を解く

        Args:
            question: 質問
            examples: Few-shot例(オプション)

        Returns:
            final_answer: 多数決による最終回答
            confidence: 信頼度
            all_answers: 全ての回答
        """
        # プロンプトを構築
        if examples:
            examples_text = "\n\n".join([
                f"Q: {ex['question']}\nA: {ex['reasoning']}\n答え: {ex['answer']}"
                for ex in examples
            ])
            prompt = f"{examples_text}\n\nQ: {question}\nA: ステップバイステップで考えましょう。"
        else:
            prompt = f"Q: {question}\nA: ステップバイステップで考えましょう。"

        # 複数の推論パスを生成
        answers = []
        reasonings = []

        for _ in range(self.n_paths):
            reasoning = self.llm_client.generate(
                prompt,
                temperature=self.temperature
            )
            answer = self.extract_answer(reasoning)

            if answer is not None:
                answers.append(answer)
                reasonings.append(reasoning)

        # 多数決
        if not answers:
            return None, 0.0, []

        answer_counts = Counter(answers)
        final_answer, count = answer_counts.most_common(1)[0]
        confidence = count / len(answers)

        return final_answer, confidence, answers

# 使用例
sc_cot = SelfConsistencyCoT(llm_client, n_paths=5)

question = "農場に羊が17匹いました。5匹売り、12匹買いました。今何匹いますか?"
final_answer, confidence, all_answers = sc_cot.solve(question)

print(f"全ての回答: {all_answers}")
print(f"最終回答: {final_answer}")
print(f"信頼度: {confidence:.2f}")

Tree of Thoughts(ToT)

アルゴリズム

Tree of Thoughts(ToT)は、推論を木構造で探索する拡張版CoTです。

  1. 複数の思考ステップを生成
  2. 各ステップを評価
  3. 有望なパスを選択して展開
  4. 最終解に到達するまで繰り返し

探索アルゴリズム

幅優先探索(BFS):

class TreeOfThoughts:
    """Tree of Thoughts"""

    def __init__(self, llm_client, n_branches=3, max_depth=5):
        self.llm_client = llm_client
        self.n_branches = n_branches
        self.max_depth = max_depth

    def generate_thoughts(self, state, question):
        """現在の状態から複数の思考を生成"""
        prompt = f"""問題: {question}

これまでの思考:
{state}

次に考えられるステップを{self.n_branches}つ提案してください:"""

        thoughts = []
        for i in range(self.n_branches):
            thought = self.llm_client.generate(prompt, temperature=0.8)
            thoughts.append(thought)

        return thoughts

    def evaluate_thought(self, state, thought, question):
        """思考の有望さを評価"""
        prompt = f"""問題: {question}

これまでの思考:
{state}

新しいステップ: {thought}

このステップは問題解決に向けて正しい方向ですか?
1(悪い)から10(良い)で評価してください:"""

        score_text = self.llm_client.generate(prompt)
        # スコアを抽出
        try:
            score = int(''.join(filter(str.isdigit, score_text[:5])))
            return min(10, max(1, score))
        except:
            return 5

    def is_solution(self, state, question):
        """解に到達したか確認"""
        prompt = f"""問題: {question}

思考プロセス:
{state}

この思考プロセスで問題は解決しましたか?「はい」または「いいえ」で答えてください:"""

        response = self.llm_client.generate(prompt)
        return "はい" in response

    def solve_bfs(self, question):
        """BFSで解を探索"""
        queue = [("", 0)]  # (state, depth)
        best_solution = None

        while queue:
            state, depth = queue.pop(0)

            if depth >= self.max_depth:
                continue

            if self.is_solution(state, question):
                return state

            # 思考を生成
            thoughts = self.generate_thoughts(state, question)

            # 評価してソート
            scored_thoughts = [
                (t, self.evaluate_thought(state, t, question))
                for t in thoughts
            ]
            scored_thoughts.sort(key=lambda x: -x[1])

            # 上位を展開
            for thought, score in scored_thoughts[:2]:
                new_state = state + "\n" + thought if state else thought
                queue.append((new_state, depth + 1))

        return best_solution

# 使用例
# tot = TreeOfThoughts(llm_client)
# solution = tot.solve_bfs("24ゲーム: 4,5,6,7を使って24を作ってください")

CoTの性能分析

効果的なタスク

CoTが特に効果的なタスク:

タスク 改善幅
算術推論 +40-60%
常識推論 +20-30%
記号推論 +30-50%
複数ステップQA +20-40%

モデルサイズの影響

CoTの効果はモデルサイズに依存します。

$$ \text{CoT Improvement} \propto \log(\text{Model Size}) $$

約100B パラメータ以上のモデルで顕著な効果が見られます(Emergent Ability)。

計算コスト

CoTは出力トークン数を増加させるため、推論コストが増加します。

$$ \text{Cost}_{\text{CoT}} = \text{Cost}_{\text{Direct}} \times \frac{|\text{CoT}| + |\text{Answer}|}{|\text{Answer}|} $$

まとめ

本記事では、Chain-of-Thought推論の仕組みと実装を解説しました。

  • Few-shot CoT: 推論例を含むプロンプトでCoTを誘発
  • Zero-shot CoT: 「Let’s think step by step」でCoTを誘発
  • Self-Consistency: 複数パスの多数決で精度向上
  • Tree of Thoughts: 木構造探索による発展版

次のステップとして、以下の記事も参考にしてください。