콘텐츠로 이동

Self-Refine와 Self-Consistency: 반복 개선과 표본 투표

에이전트가 외부 도구 없이도 출력 품질을 높이는 방법이 있다. 자신의 출력을 비판하고 수정하는 Self-Refine과, 여러 독립 추론 경로의 다수결을 취하는 Self-Consistency가 그것이다. 두 패턴은 접근 방식이 다르지만 같은 목표를 향한다. 단일 샘플링의 분산을 줄이고 최종 출력의 신뢰도를 높이는 것이다.

Self-Refine: Generate → Critique → Refine

섹션 제목: “Self-Refine: Generate → Critique → Refine”

Self-Refine(Madaan et al., NeurIPS 2023)은 세 단계가 반복되는 단순한 루프다.

┌───────────────────────────────────────────────────────────┐
│ Self-Refine 루프 │
│ │
│ 입력 태스크 │
│ ▼ │
│ ┌──────────┐ │
│ │ Generate │ 초기 답변 생성 │
│ └────┬─────┘ │
│ ▼ │
│ ┌──────────┐ │
│ │ Critique │ 동일 모델이 답변의 문제점 비판 │
│ └────┬─────┘ (명확성, 정확성, 완성도 등) │
│ ▼ │
│ ┌──────────┐ │
│ │ Refine │ 비판을 반영해 답변 수정 │
│ └────┬─────┘ │
│ ▼ │
│ 종료 조건 충족? (max 이터레이션 또는 "충분히 좋음") │
│ NO → Critique로 돌아감 │
│ YES → 최종 답변 출력 │
└───────────────────────────────────────────────────────────┘

세 단계 모두 동일한 모델이 수행한다는 점이 핵심이다. 별도 평가 모델이 필요 없고, 추가 학습도 없다. 모델의 Self-Feedback 능력만으로 품질을 끌어올린다.

개념 이해용 의사 코드이며 실제 API와 다를 수 있습니다.

def self_refine(model, task: str, max_iterations: int = 3) -> str:
"""
동일 모델이 생성→비판→수정을 반복한다.
"""
# 1단계: 초기 생성
output = model.generate([
{"role": "user", "content": task}
]).text
for i in range(max_iterations):
# 2단계: 비판 (Critique)
critique_prompt = (
f"아래 답변을 비판하라. 구체적인 문제점과 개선 방향을 제시하라. "
f"답변이 충분히 좋으면 '개선 필요 없음'이라고만 답하라.\n\n"
f"원래 태스크: {task}\n\n"
f"현재 답변:\n{output}"
)
critique = model.generate([
{"role": "user", "content": critique_prompt}
]).text
# 종료 조건: 모델이 더 이상 개선 필요 없다고 판단
if "개선 필요 없음" in critique:
break
# 3단계: 수정 (Refine)
refine_prompt = (
f"아래 비판을 반영해 답변을 개선하라.\n\n"
f"원래 태스크: {task}\n\n"
f"현재 답변:\n{output}\n\n"
f"비판:\n{critique}"
)
output = model.generate([
{"role": "user", "content": refine_prompt}
]).text
return output

Self-Refine 논문은 대화 생성, 코드 최적화, 수학 추론 등 7개 태스크에서 평균 약 +20% absolute 개선을 보고했다. 단, 이 수치는 태스크마다 편차가 크고, 평가 기준이 다양하므로 맥락 없이 인용할 때 주의가 필요하다.

가장 흔한 실패 모드는 **아첨적 비판(sycophantic critique)**이다. 모델은 자신이 생성한 답변을 비판할 때 실제로 잘못된 부분이 있어도 “대체로 잘 작성됐습니다” 같은 긍정적 평가를 내놓는 경향이 있다. 이를 방지하기 위해 “반드시 세 가지 구체적 약점을 찾아라”처럼 비판 프롬프트에 의무 비판 조건을 넣는 방식이 사용된다.

Self-Consistency: Fan-Out 샘플링과 다수결

섹션 제목: “Self-Consistency: Fan-Out 샘플링과 다수결”

Self-Consistency(Wang et al., ICLR 2023)는 완전히 다른 전략을 취한다. 개선하는 대신 여러 번 독립적으로 샘플링하고, 가장 많이 등장한 답을 채택한다.

┌──────────────────────────────────────────────────────────────┐
│ Self-Consistency 흐름 │
│ │
│ 동일 입력 태스크 │
│ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Fan-out: N개 독립 샘플 생성 (temperature > 0)│ │
│ └──────────────────────────────────────────────┘ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │샘플 1 │ │샘플 2 │ │샘플 3 │ │샘플 N │ │
│ │추론:... │ │추론:... │ │추론:... │ │추론:... │ │
│ │답: A │ │답: B │ │답: A │ │답: A │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
│ └───────────┴───────────┴────────────┘ │
│ ▼ │
│ 다수결 집계: A(3표) > B(1표) → 최종 답: A │
└──────────────────────────────────────────────────────────────┘

Self-Consistency의 아이디어는 단순하다. 올바른 추론 경로는 여러 독립 샘플에서도 같은 최종 답으로 수렴하지만, 잘못된 추론 경로는 다양한 오답으로 분산된다. 따라서 다수결이 단일 샘플보다 신뢰할 수 있다.

개념 이해용 의사 코드이며 실제 API와 다를 수 있습니다.

from collections import Counter
def self_consistency(
model,
task: str,
n_samples: int = 10,
temperature: float = 0.7,
) -> str:
"""
N개의 독립 샘플을 생성하고 다수결로 최종 답을 결정한다.
"""
answers = []
for _ in range(n_samples):
# 각 샘플은 독립적: 이전 샘플의 영향 없음
response = model.generate(
messages=[{"role": "user", "content": task}],
temperature=temperature, # 다양성 확보를 위해 0보다 큰 값
)
# 최종 답만 추출 (추론 과정은 버림)
answer = extract_final_answer(response.text)
answers.append(answer)
# 다수결
counter = Counter(answers)
majority_answer, vote_count = counter.most_common(1)[0]
return majority_answer

Self-Consistency의 검증된 성능 수치

섹션 제목: “Self-Consistency의 검증된 성능 수치”
벤치마크 개선 수치
GSM8K (초등 수학) +17.9% absolute
SVAMP (수학 응용) +11.0% absolute
AQuA (대수 추론) +12.2% absolute

수학·논리 추론처럼 정답이 명확히 하나인 태스크에서 특히 효과적이다. 반면 창의적 글쓰기나 열린 질문에서는 “다수결”이라는 집계 방식 자체가 의미 없다. 여러 창의적 답 중 어느 것이 “더 맞다”고 말할 수 없기 때문이다.

차원 Self-Refine Self-Consistency
핵심 메커니즘 반복 비판·수정 Fan-out 후 다수결
LLM 호출 수 이터레이션 × 2~3 N 샘플 (병렬 가능)
병렬화 가능 여부 불가 (순차 의존) 가능
적합 태스크 오픈엔드, 글쓰기, 코드 품질 수학, 논리, 단일 정답
주요 실패 모드 아첨적 비판 분산 과다 (N이 작을 때)
비용 구조 적은 이터레이션이면 저렴 N에 비례해 선형 증가

Self-Refine에서: Critique 단계에 도메인별 루브릭(채점 기준표)을 제공하면 아첨적 비판을 줄일 수 있다. 예를 들어 코드 리뷰라면 “보안 취약점, 성능 병목, 가독성 문제를 각각 하나씩 반드시 찾아라”는 식이다.

Self-Consistency에서: N을 너무 작게(5 미만) 설정하면 다수결의 통계적 신뢰성이 떨어진다. 1020이 실용적 범위다. 또한 온도(temperature)가 너무 낮으면 샘플 간 다양성이 줄어 자동으로 같은 답만 생성되어 다수결 효과가 사라진다. 일반적으로 0.50.8이 적절하다.

다음 챕터에서는 이 두 패턴보다 훨씬 체계적인 탐색 공간 구조를 사용하는 Tree of Thoughts와 Graph of Thoughts를 살펴본다. 더 넓은 가능성 공간을 탐색하되, 비용이 폭발하지 않도록 제어하는 방법을 다룬다.

참고 자료