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 유도:
- Top-k 토큰 중 첫 토큰을 다양하게 선택
- 각 경로의 최종 답변 확인
- 경로 신뢰도 기반 가중 투표
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.