콘텐츠로 이동
Data Prep
상세

Chain-of-Thought Reasoning

중간 추론 단계를 생성하여 대형 언어 모델의 복잡한 추론 능력을 향상시키는 기법.


개요

항목 내용
분야 LLM Reasoning, Prompt Engineering
핵심 논문 Wei et al., 2022 (Google)
발표 NeurIPS 2022
핵심 아이디어 최종 답변 전에 단계별 추론 과정을 명시적으로 생성
적용 대상 수학, 상식 추론, 기호 추론, 코드 생성

핵심 개념

Chain-of-Thought (CoT) 정의

일반적인 프롬프팅에서는 입력에 대해 직접 답변을 생성하지만, CoT는 중간 추론 단계(intermediate reasoning steps)를 명시적으로 생성한 후 최종 답변에 도달한다.

[Standard Prompting]
Q: Roger has 5 tennis balls. He buys 2 cans of 3. How many does he have?
A: 11

[Chain-of-Thought Prompting]
Q: Roger has 5 tennis balls. He buys 2 cans of 3. How many does he have?
A: Roger started with 5 balls. 2 cans of 3 = 6 balls. 5 + 6 = 11. The answer is 11.

Emergent Ability

CoT 추론 능력은 모델 규모와 밀접하게 연관된다:

  • 10B 파라미터 미만: CoT 효과 미미 또는 역효과
  • 100B+ 파라미터: 유의미한 성능 향상
  • 540B (PaLM): GSM8K에서 SOTA 달성

이는 "emergent ability"의 대표적 사례로, 특정 규모 이상에서만 능력이 발현된다.


주요 기법 및 변형

1. Few-shot CoT Prompting (원본)

프롬프트에 CoT 예시를 포함:

def few_shot_cot_prompt(question: str) -> str:
    examples = """
Q: There are 15 trees in the grove. Grove workers plant trees today. 
After they are done, there will be 21 trees. How many trees did they plant?
A: There are 15 trees originally. Then there were 21 trees after planting. 
So they planted 21 - 15 = 6 trees. The answer is 6.

Q: If there are 3 cars in the parking lot and 2 more cars arrive, 
how many cars are in the parking lot?
A: There are originally 3 cars. 2 more cars arrive. 
So there are 3 + 2 = 5 cars. The answer is 5.
"""
    return f"{examples}\nQ: {question}\nA:"

2. Zero-shot CoT

Wei et al. (2022) 이후, Kojima et al. (2022)은 단순히 "Let's think step by step"을 추가하는 것만으로 CoT 유도:

def zero_shot_cot_prompt(question: str) -> str:
    return f"Q: {question}\nA: Let's think step by step."

3. Self-Consistency (Wang et al., 2023)

여러 추론 경로를 샘플링하고 다수결 투표로 최종 답변 결정:

import openai
from collections import Counter

def self_consistency_cot(
    question: str, 
    n_samples: int = 5,
    temperature: float = 0.7
) -> str:
    prompt = zero_shot_cot_prompt(question)

    responses = []
    for _ in range(n_samples):
        response = openai.ChatCompletion.create(
            model="gpt-4",
            messages=[{"role": "user", "content": prompt}],
            temperature=temperature
        )
        answer = extract_final_answer(response.choices[0].message.content)
        responses.append(answer)

    # Majority voting
    answer_counts = Counter(responses)
    return answer_counts.most_common(1)[0][0]

def extract_final_answer(text: str) -> str:
    """추론 텍스트에서 최종 답변 추출"""
    if "The answer is" in text:
        return text.split("The answer is")[-1].strip().rstrip(".")
    return text.split()[-1]

4. Tree-of-Thought (ToT)

Yao et al. (2023)이 제안한 방식으로, 선형적 CoT 대신 트리 구조로 여러 사고 경로를 탐색:

from dataclasses import dataclass
from typing import List

@dataclass
class ThoughtNode:
    thought: str
    score: float
    children: List['ThoughtNode']

def tree_of_thought(
    question: str,
    depth: int = 3,
    breadth: int = 3,
    evaluator_fn = None
) -> str:
    """Tree-of-Thought 탐색"""
    root = ThoughtNode(thought=question, score=0.0, children=[])

    def expand_node(node: ThoughtNode, current_depth: int):
        if current_depth >= depth:
            return

        # Generate multiple thoughts
        thoughts = generate_thoughts(node.thought, n=breadth)

        for thought in thoughts:
            score = evaluator_fn(thought) if evaluator_fn else 0.0
            child = ThoughtNode(thought=thought, score=score, children=[])
            node.children.append(child)

            # Prune low-score branches
            if score > 0.5:
                expand_node(child, current_depth + 1)

    expand_node(root, 0)
    return find_best_path(root)

def generate_thoughts(context: str, n: int) -> List[str]:
    """다음 추론 단계 후보 생성"""
    # LLM 호출로 n개의 다음 단계 생성
    pass

def find_best_path(root: ThoughtNode) -> str:
    """최고 점수 경로 탐색 (BFS/DFS)"""
    pass

5. Program-of-Thought (PoT)

Chen et al. (2022)이 제안. 자연어 추론 대신 코드로 추론 후 실행:

def program_of_thought(question: str) -> str:
    """자연어 문제를 Python 코드로 변환하여 실행"""

    prompt = f"""
Solve the following problem by writing Python code.
Only output the Python code, nothing else.

Problem: {question}

Python code:
"""

    response = openai.ChatCompletion.create(
        model="gpt-4",
        messages=[{"role": "user", "content": prompt}]
    )

    code = response.choices[0].message.content

    # 안전한 코드 실행
    result = safe_exec(code)
    return str(result)

def safe_exec(code: str) -> any:
    """제한된 환경에서 코드 실행"""
    allowed_builtins = {
        'abs': abs, 'round': round, 'sum': sum,
        'min': min, 'max': max, 'len': len,
        'range': range, 'int': int, 'float': float
    }
    exec_globals = {"__builtins__": allowed_builtins}
    exec(code, exec_globals)
    return exec_globals.get('answer')

6. Chain of Preference Optimization (CPO)

NeurIPS 2024. CoT 경로에 대한 선호도 최적화로 더 나은 추론 학습:

def cpo_training_step(
    model,
    question: str,
    preferred_cot: str,
    rejected_cot: str,
    beta: float = 0.1
):
    """CPO 학습 단계 (DPO 변형)"""

    # 선호/비선호 CoT에 대한 로그 확률 계산
    log_prob_preferred = model.log_prob(question, preferred_cot)
    log_prob_rejected = model.log_prob(question, rejected_cot)

    # Reference 모델 (frozen)
    with torch.no_grad():
        ref_log_prob_preferred = ref_model.log_prob(question, preferred_cot)
        ref_log_prob_rejected = ref_model.log_prob(question, rejected_cot)

    # DPO/CPO Loss
    log_ratio_preferred = log_prob_preferred - ref_log_prob_preferred
    log_ratio_rejected = log_prob_rejected - ref_log_prob_rejected

    loss = -F.logsigmoid(beta * (log_ratio_preferred - log_ratio_rejected))
    return loss.mean()

벤치마크 성능

GSM8K (수학 문제)

모델 Standard CoT Self-Consistency
GPT-3 175B 18.0% 46.9% -
PaLM 540B 17.9% 56.9% 74.4%
GPT-4 - 92.0% 97.0%

MATH (경쟁 수학)

모델 Standard CoT
GPT-4 23.0% 42.5%
Claude 3 Opus - 60.1%
o1-preview - 85.5%

CoT 없이 추론 유도 (NeurIPS 2024)

Wang et al. (2024)는 프롬프팅 없이 디코딩 전략만으로 CoT 유도:

  1. Top-k 토큰 중 첫 토큰을 다양하게 선택
  2. 각 경로의 최종 답변 확인
  3. 경로 신뢰도 기반 가중 투표
def cot_decoding(model, question: str, k: int = 10) -> str:
    """CoT 디코딩: 프롬프팅 없이 추론 경로 유도"""

    input_ids = tokenizer.encode(question, return_tensors="pt")

    # 첫 토큰 후보들
    first_token_logits = model(input_ids).logits[0, -1, :]
    top_k_tokens = torch.topk(first_token_logits, k).indices

    paths = []
    for token_id in top_k_tokens:
        # 각 첫 토큰으로 시작하는 경로 생성
        path_ids = torch.cat([input_ids, token_id.unsqueeze(0).unsqueeze(0)], dim=1)
        output = model.generate(path_ids, max_new_tokens=256)
        path_text = tokenizer.decode(output[0])
        paths.append({
            'text': path_text,
            'confidence': compute_path_confidence(model, output)
        })

    # 답변 추출 및 가중 투표
    return weighted_vote(paths)

정보 이론적 분석 (2025)

ICML 2025에서 CoT를 정보 이론으로 분석:

  • 상호 정보량(MI): CoT 단계와 정답 간의 MI가 높을수록 유효한 추론
  • 실패 모드 탐지: MI 감소 패턴으로 추론 실패 조기 탐지
def compute_cot_mutual_information(
    model,
    question: str,
    cot_steps: List[str],
    answer: str
) -> float:
    """CoT 단계와 정답 간의 상호 정보량 추정"""

    # P(answer | question)
    p_answer_given_q = model.prob(question, answer)

    # P(answer | question + cot)
    cot_text = " ".join(cot_steps)
    p_answer_given_q_cot = model.prob(question + cot_text, answer)

    # MI 추정 (pointwise)
    mi = np.log(p_answer_given_q_cot / p_answer_given_q)
    return mi

한계 및 비판

1. 추론의 충실성(Faithfulness) 문제

  • CoT가 실제 모델 내부 계산을 반영하는지 불명확
  • "Post-hoc rationalization" 가능성

2. 데이터 분포 의존성 (2025 연구)

Zhao et al. (2025)의 분석:

  • CoT 성능은 학습 데이터의 추론 패턴 분포에 크게 의존
  • OOD(Out-of-Distribution) 문제에서 CoT 효과 급감
  • "CoT가 실제 추론인가, 패턴 매칭인가?"에 대한 논쟁

3. 계산 비용

  • 토큰 수 증가로 인한 latency 및 비용 증가
  • 실시간 애플리케이션에서의 trade-off

실용적 구현 가이드

1. 효과적인 CoT 프롬프트 설계

def design_cot_prompt(
    task_type: str,
    examples: List[dict],
    include_format_hint: bool = True
) -> str:
    """태스크별 CoT 프롬프트 생성"""

    format_hints = {
        "math": "Show your calculations step by step.",
        "logic": "Reason through each premise carefully.",
        "code": "Break down the algorithm into steps.",
        "commonsense": "Consider the relevant facts one by one."
    }

    prompt_parts = []

    # 태스크 설명
    if include_format_hint:
        prompt_parts.append(f"Instructions: {format_hints.get(task_type, '')}")

    # Few-shot 예시
    for ex in examples:
        prompt_parts.append(f"Q: {ex['question']}")
        prompt_parts.append(f"A: {ex['reasoning']} Therefore, {ex['answer']}.")

    return "\n\n".join(prompt_parts)

2. 프로덕션 파이프라인

from typing import Optional
import asyncio

class CoTReasoner:
    """프로덕션용 CoT 추론 파이프라인"""

    def __init__(
        self,
        model: str = "gpt-4",
        use_self_consistency: bool = True,
        n_samples: int = 5,
        timeout: float = 30.0
    ):
        self.model = model
        self.use_self_consistency = use_self_consistency
        self.n_samples = n_samples
        self.timeout = timeout

    async def reason(
        self, 
        question: str,
        context: Optional[str] = None
    ) -> dict:
        """비동기 CoT 추론"""

        prompt = self._build_prompt(question, context)

        if self.use_self_consistency:
            # 병렬 샘플링
            tasks = [
                self._sample_reasoning(prompt) 
                for _ in range(self.n_samples)
            ]
            results = await asyncio.gather(*tasks)

            # 다수결
            final_answer = self._majority_vote(results)
            confidence = self._compute_confidence(results, final_answer)
        else:
            result = await self._sample_reasoning(prompt)
            final_answer = result['answer']
            confidence = result.get('confidence', 0.0)

        return {
            'question': question,
            'answer': final_answer,
            'confidence': confidence,
            'reasoning': results[0]['reasoning'] if results else None
        }

    def _build_prompt(
        self, 
        question: str, 
        context: Optional[str]
    ) -> str:
        base = "Let's solve this step by step.\n\n"
        if context:
            base += f"Context: {context}\n\n"
        base += f"Question: {question}\n\nSolution:"
        return base

    async def _sample_reasoning(self, prompt: str) -> dict:
        """단일 추론 샘플 생성"""
        # 실제 LLM API 호출
        pass

    def _majority_vote(self, results: List[dict]) -> str:
        """다수결 투표"""
        answers = [r['answer'] for r in results]
        return Counter(answers).most_common(1)[0][0]

    def _compute_confidence(
        self, 
        results: List[dict], 
        final_answer: str
    ) -> float:
        """신뢰도 계산"""
        matching = sum(1 for r in results if r['answer'] == final_answer)
        return matching / len(results)

핵심 논문

연도 논문 기여
2022 Chain-of-Thought Prompting (Wei et al.) 원본 CoT 제안
2022 Zero-shot CoT (Kojima et al.) "Let's think step by step"
2023 Self-Consistency (Wang et al.) 다중 경로 샘플링 + 투표
2023 Tree-of-Thought (Yao et al.) 트리 구조 탐색
2023 Program-of-Thought (Chen et al.) 코드 기반 추론
2024 CoT Without Prompting (Wang et al.) 디코딩 기반 CoT
2024 Chain of Preference Optimization CoT + 선호도 최적화
2025 Understanding CoT via Information Theory MI 기반 분석
2025-26 Is CoT a Mirage? (Zhao et al.) 데이터 분포 의존성 분석

참고 자료

  • Wei et al. (2022). Chain-of-Thought Prompting Elicits Reasoning in Large Language Models. NeurIPS.
  • Kojima et al. (2022). Large Language Models are Zero-Shot Reasoners. NeurIPS.
  • Wang et al. (2023). Self-Consistency Improves Chain of Thought Reasoning. ICLR.
  • Yao et al. (2023). Tree of Thoughts: Deliberate Problem Solving with LLMs. NeurIPS.
  • Chen et al. (2022). Program of Thoughts Prompting. arXiv.
  • Wang et al. (2024). Chain-of-Thought Reasoning Without Prompting. NeurIPS.
  • Zhao et al. (2025). Is Chain-of-Thought Reasoning of LLMs a Mirage? arXiv:2508.01191.