Published on

Self-Consistency CoT - k샘플·투표로 정답률 올리기

Authors

서로 다른 추론 경로를 여러 번 생성해 놓고, 그중 가장 일관되게 등장하는 답을 선택하면 왜 정확도가 오를까요? Self-Consistency CoT는 이 직관을 체계화한 기법입니다. 단일 CoT는 우연히 잘못된 경로로 빠지면 그대로 오답이 되지만, k번 샘플링하면 오류가 분산되고 다수결이 잡음을 상쇄합니다. 특히 수학·논리·멀티스텝 의사결정처럼 “경로 의존적” 문제에서 효과가 큽니다.

이 글에서는 Self-Consistency CoT의 핵심 아이디어, k-샘플링과 투표 설계, 비용 대비 효율 튜닝, 그리고 RAG/프로덕션 적용 시의 함정을 코드와 함께 정리합니다.

Self-Consistency CoT란 무엇인가

Self-Consistency는 간단히 말해 아래 절차입니다.

  1. 동일한 질문에 대해 CoT를 k번 생성한다(샘플링을 켠다).
  2. 각 응답에서 “최종 답(final answer)”을 추출한다.
  3. 최종 답을 기준으로 다수결(또는 가중 투표)로 하나를 고른다.

여기서 중요한 포인트는 “CoT의 문장 자체를 합치는 것”이 아니라 “최종 답을 합의(consensus)로 고른다”는 점입니다. 즉, 경로는 다양하되 결론이 반복되는 답을 채택합니다.

왜 단일 CoT보다 강한가

  • 단일 샘플은 편향된 한 경로에 올인합니다.
  • 샘플링을 통해 서로 다른 경로를 탐색하면, 일부 경로가 실패해도 다른 경로가 성공할 확률이 생깁니다.
  • 다수결은 독립적인 오류를 평균화합니다(오류가 완전히 독립은 아니지만, 온도와 프롬프트 설계로 다양성을 확보하면 효과가 납니다).

언제 효과가 크고, 언제 미묘한가

잘 맞는 케이스

  • 수학/계산/논리 퍼즐
  • 단계적 계획 수립(제약조건이 많은 일정/조합 최적화의 근사)
  • 코드 리뷰나 버그 원인 추론처럼 “가능한 가설”이 여러 개인 문제

효과가 제한적인 케이스

  • 사실 기반 단답(예: 특정 날짜/고유명사)처럼 샘플링이 오히려 환각을 늘릴 수 있는 문제
  • 답이 길고 서술형이며 정답 판정이 애매한 문제(투표 기준을 만들기 어렵습니다)

이런 경우에는 Self-Consistency보다 검색 근거를 강화하는 RAG, 혹은 리랭킹/검증기를 붙이는 편이 낫습니다. RAG 성능을 끌어올리는 튜닝은 RAG용 Qdrant HNSW 튜닝 실전 가이드도 함께 참고하면 좋습니다.

핵심 설계 1: k, temperature, top_p는 어떻게 잡나

Self-Consistency의 성패는 “다양한 경로를 충분히 뽑되, 무의미한 랜덤성으로 붕괴하지 않게” 균형을 잡는 데 있습니다.

실무에서 자주 쓰는 시작점

  • k: 5 또는 7부터 시작(비용 대비 효율이 좋습니다)
  • temperature: 0.7 전후(논리 문제는 0.5부터)
  • top_p: 0.9 전후

k를 늘리면 무조건 좋나

아닙니다.

  • 초반에는 정확도가 빠르게 오르다가, 어느 순간부터 수익 체감이 옵니다.
  • 지연시간과 비용이 선형으로 증가합니다.

실전에서는 목표 SLO(예: p95 2초)와 토큰 비용 한도를 먼저 정하고, 그 안에서 k를 최대한 키우는 방식이 현실적입니다.

핵심 설계 2: “무엇에 투표할 것인가”

투표 단위가 중요합니다. CoT 전체에 투표하면 문장 유사도 문제로 깨지기 쉽고, 최종 답만 투표하면 파싱이 관건입니다.

권장: 구조화된 최종 답 강제

프롬프트에서 최종 답을 JSON으로 내게 하면 투표가 쉬워집니다. 단, MDX 빌드 이슈를 피하려면 본문에서 부등호는 반드시 인라인 코드로 처리해야 합니다.

아래는 “추론은 자유롭게, 최종 답은 JSON” 패턴입니다.

지시사항:
- 문제를 단계적으로 풀되, 최종 답은 반드시 아래 JSON 스키마로만 출력하세요.
- JSON 외 텍스트를 출력하지 마세요.

출력 스키마:
{
  "final": "정답(문자열)",
  "confidence": 0.0
}

이후 final 필드만 모아 다수결을 하면 됩니다.

숫자/식 답안은 정규화가 필수

예를 들어 1/2, 0.5, 50%는 같은 의미지만 문자열 투표는 서로 다른 값으로 취급합니다. 따라서 투표 전에 정규화(normalization)를 해야 합니다.

  • 공백 제거, 소수점 표준화
  • 분수 a/b를 유리수로 변환
  • 퍼센트는 소수로 변환

구현 예제: Python으로 k-샘플링 후 다수결

아래 예시는 OpenAI 계열 API 호출을 가정한 “패턴 코드”입니다. 실제 SDK/모델명은 환경에 맞게 바꾸면 됩니다.

import json
import re
from collections import Counter


def normalize_answer(ans: str) -> str:
    ans = ans.strip()
    ans = re.sub(r"\s+", "", ans)

    # 간단 예시: 퍼센트 정규화
    if ans.endswith("%"):
        try:
            v = float(ans[:-1]) / 100.0
            return str(v)
        except ValueError:
            pass

    return ans


def majority_vote(final_answers):
    normed = [normalize_answer(a) for a in final_answers]
    c = Counter(normed)
    top, cnt = c.most_common(1)[0]
    return top, cnt / len(normed)


def extract_final_json(text: str):
    # 모델이 JSON만 출력한다는 가정이지만, 방어적으로 파싱
    text = text.strip()
    return json.loads(text)


def self_consistency_cot(client, prompt: str, k: int = 7, temperature: float = 0.7):
    finals = []
    raw = []

    for _ in range(k):
        resp = client.responses.create(
            model="gpt-4.1-mini",
            input=prompt,
            temperature=temperature,
        )
        out_text = resp.output_text
        raw.append(out_text)

        obj = extract_final_json(out_text)
        finals.append(str(obj.get("final", "")))

    voted, vote_ratio = majority_vote(finals)
    return {
        "voted_final": voted,
        "vote_ratio": vote_ratio,
        "finals": finals,
        "raw": raw,
    }

비용 최적화 팁: 병렬 호출

k번을 직렬로 호출하면 지연시간이 커집니다. 서버 환경에서는 비동기/병렬로 쏘는 것이 일반적입니다.

  • Python: asyncio.gather
  • Node.js: Promise.all

다만 과도한 병렬화는 레이트리밋에 걸리기 쉽습니다. 결제/크레딧/쿼터 이슈가 있다면 OpenAI Responses API 402 결제·크레딧 오류 해결처럼 운영 관점의 점검도 같이 해두는 편이 안전합니다.

고급: 단순 다수결보다 나은 “가중 투표”

다수결은 강력하지만, 모든 샘플을 동일 가중치로 취급합니다. 실전에서는 아래 신호로 가중치를 줄 수 있습니다.

1) 자체 확신(confidence) 가중

모델이 confidence를 내게 하고, 이를 가중치로 투표합니다. 단, 모델의 자기 확신은 잘못 보정되어 있을 수 있으니(과신) 맹신은 금물입니다.

2) 검증기(verifier) 점수 가중

  • 수학이면 정답을 대입해 검산
  • 코드면 테스트 실행
  • 규칙 기반 제약조건 검사

검증기를 붙이면 Self-Consistency가 “샘플링 앙상블”에서 “샘플링 + 선택”으로 진화합니다.

3) 로그확률 기반(가능한 경우)

일부 API/모델은 토큰 로그확률을 제공하므로, 최종 답의 likelihood를 근거로 가중치를 줄 수 있습니다.

RAG와 결합할 때의 주의점

RAG 파이프라인에서 Self-Consistency를 붙일 때 흔히 하는 실수는 “매 샘플마다 검색 결과가 달라져서 투표가 의미 없어지는” 상황입니다.

패턴 A: 검색은 1회, 생성만 k회(권장)

  1. 쿼리로 검색을 한 번 수행
  2. 동일한 컨텍스트를 고정
  3. 생성만 샘플링해서 k개 답을 뽑고 투표

이렇게 해야 투표가 “추론 다양성”을 반영합니다.

패턴 B: 검색도 k회(주의)

검색도 샘플링하면 컨텍스트가 바뀌면서 답의 분산이 커집니다. 이 경우 투표가 “정답 합의”가 아니라 “서로 다른 근거의 혼합”이 되어 오히려 품질이 흔들릴 수 있습니다.

임베딩/인덱스 변경 등으로 검색 분포 자체가 흔들리는 환경이라면, 임베딩 교체/드리프트 관리 전략도 함께 필요합니다. 관련해서는 Pinecone·Milvus 임베딩 드리프트 탐지와 리인덱싱도 연결해서 보면 전체 파이프라인 안정화에 도움이 됩니다.

프롬프트 패턴: CoT를 노출하지 않으면서도 Self-Consistency 하기

요즘은 정책/보안/제품 요구로 CoT를 그대로 노출하지 않는 경우가 많습니다. 그럴 때는 “내부 추론은 하되, 출력은 짧게”를 강제하고, 여전히 k-샘플링과 투표를 적용할 수 있습니다.

요구사항:
- 내부적으로 단계적 추론을 수행하되, 사용자에게는 추론 과정을 공개하지 마세요.
- 최종 답만 JSON으로 출력하세요.

출력:
{
  "final": "...",
  "confidence": 0.0
}

이 패턴의 장점은 다음과 같습니다.

  • 사용자 출력이 짧아 토큰 비용이 줄어듭니다.
  • 투표 파싱이 안정적입니다.
  • CoT 노출 리스크를 줄입니다.

운영 관점 체크리스트

Self-Consistency는 “정확도”와 “비용/지연”을 맞바꾸는 기법입니다. 프로덕션에서는 아래를 지표로 관리하는 것이 좋습니다.

1) k별 정확도 곡선

  • k=1,3,5,7,9에서 오프라인 평가
  • 수익 체감 구간을 찾아 기본값으로 고정

2) vote ratio 분포

위 코드의 vote_ratio 같은 합의 강도는 품질 신호가 됩니다.

  • vote_ratio가 높으면(예: 0.7 이상) 답이 안정적
  • 낮으면(예: 0.4 이하) 불확실, 재질문/추가 검색/검증기 실행 트리거로 사용

3) 실패 모드 로깅

  • 파싱 실패(JSON 깨짐)
  • 답 정규화 실패
  • 레이트리밋/타임아웃

이런 실패는 “모델 성능”이 아니라 “시스템 품질” 문제로 이어지므로 별도 알람을 두는 것이 좋습니다.

자주 하는 실수와 해결책

실수 1: 온도를 너무 낮게 둠

temperature=0.0에 가까우면 k번을 뽑아도 거의 같은 답이 나와 Self-Consistency가 의미가 없어집니다.

  • 해결: temperature를 올리거나 top_p를 조정해 경로 다양성을 확보

실수 2: 최종 답 추출이 불안정

자연어로 “정답은 … 입니다”처럼 나오면 파싱이 흔들립니다.

  • 해결: JSON 스키마 강제 + 엄격 파서 + 재시도

실수 3: 투표 기준이 애매한 서술형 문제에 적용

서술형 답은 다수결이 “표현”을 뽑을 뿐 “정답”을 뽑지 못합니다.

  • 해결: 평가 함수를 정의(루브릭/체커)하거나, 핵심 주장만 구조화해 투표

정리

Self-Consistency CoT는 “한 번의 그럴듯한 추론” 대신 “여러 번의 서로 다른 추론 + 합의”로 정확도를 끌어올리는 실전형 기법입니다. 핵심은 세 가지입니다.

  • 샘플 다양성을 만드는 파라미터(k, temperature, top_p) 튜닝
  • 최종 답을 안정적으로 추출·정규화하는 출력 설계(JSON 권장)
  • 단순 다수결을 넘어, 검증기/가중 투표로 선택 품질을 강화

RAG와 결합할 때는 “검색은 고정, 생성만 k회”가 기본이며, vote_ratio 같은 합의 강도를 운영 지표로 삼으면 비용을 통제하면서도 품질을 안정화할 수 있습니다.