- Published on
Chain-of-Thought 막힘? Self-Consistency로 정확도↑
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서술형 추론을 유도하는 Chain-of-Thought(이하 CoT)는 복잡한 문제에서 성능을 끌어올리지만, 실무에서는 종종 막힘이 발생합니다. 예를 들어 계산이 길어지면서 중간 가정이 틀어지거나, 특정 단계에서 잘못된 분기(heuristic)로 빠져 끝까지 그럴듯한 오답을 만들어 내는 경우가 많습니다.
이 글에서는 CoT의 대표적인 실패 모드와, 이를 비교적 간단한 방식으로 완화하는 Self-Consistency(SC) 기법을 정리합니다. 핵심은 한 번 잘 풀게 만드는 것이 아니라 여러 번 풀게 한 뒤 가장 일관된 답을 고르는 것입니다.
또한, SC를 제품/서비스에 붙일 때 비용과 지연시간을 어떻게 통제할지, 그리고 구조화 출력(JSON)과 결합해 안정성을 높이는 방법까지 코드 예제로 다룹니다.
CoT가 막히는 전형적인 패턴
CoT는 모델이 중간 추론을 텍스트로 전개하도록 유도합니다. 하지만 이 전개가 길어질수록 오류가 누적되거나, 다음과 같은 문제를 자주 만납니다.
1) 초기 가정이 틀리면 끝까지 틀린다
모델이 초반에 잘못된 가정을 세우면 이후 단계는 그 가정 위에서 논리적으로는 일관되게 전개됩니다. 결과적으로 설명은 그럴듯한데 답이 틀린 상황이 생깁니다.
2) 한 번 선택한 분기에서 빠져나오지 못한다
탐색 관점에서 보면 CoT는 사실상 단일 경로로 추론을 진행합니다. 중간에 다른 경로가 더 유망해도 되돌아가 재탐색을 하지 못해 막히거나 오답으로 수렴합니다.
3) 계산/기호 문제에서 작은 실수가 치명적이다
수식 전개, 조건 분기, 단위 변환 등에서 한 번의 산술 실수가 전체 결과를 망칩니다. 특히 길이가 긴 계산일수록 위험합니다.
4) 불확실성을 표현하지 못하고 확정적으로 말한다
모델은 확률적으로 가장 그럴듯한 다음 토큰을 선택합니다. 불확실한 상황에서도 확정적 문장을 내보내는 경향이 있어, 막힘이 침묵이 아니라 자신감 있는 오답으로 나타납니다.
Self-Consistency란 무엇인가
Self-Consistency는 간단히 말해 다음 전략입니다.
- 동일 문제를
다양한 추론 경로로 여러 번 샘플링한다 - 최종 답을
다수결또는가중 투표로 선택한다
여기서 다양성은 보통 샘플링 파라미터(temperature, top_p)로 확보합니다. CoT가 단일 경로 탐색이라면, SC는 다중 경로 탐색 후 집계에 가깝습니다.
왜 효과가 있나
- 오답 경로는 다양하게 흩어질 가능성이 크고
- 정답 경로는 문제 구조상 특정 답으로
수렴하는 경향이 있어 - 여러 번 뽑아 투표하면 정답이 상대적으로 더 자주 등장합니다
즉, 모델을 바꾸지 않고도 추론의 분산을 활용해 정확도를 올립니다.
Self-Consistency 적용 시 설계 포인트
1) 언제 SC를 켤 것인가: 항상 vs 조건부
항상 SC를 켜면 정확도는 오르지만 비용과 지연이 증가합니다. 실무에서는 보통 조건부 SC가 효율적입니다.
- 1차 단일 호출 결과가 불확실할 때만 SC 실행
- 또는 특정 태스크(수학, 논리, 제약 충족)가 들어오면 SC 실행
불확실성 신호 예시
- 모델이 스스로
확신 낮음을 표현 - 검증기(validator)에서 제약 위반 감지
- 정답 형식 파싱 실패
구조화 출력 강제는 이 지점에서 특히 유용합니다. JSON Schema로 형식을 강제하면 파싱 실패를 SC 트리거로 삼기 쉽습니다. 관련해서는 RAG 환각을 줄이는 JSON Schema 강제 출력법도 함께 참고하면 좋습니다.
2) 샘플 수 k는 얼마나?
보편적으로 k=5 또는 k=7부터 시작합니다.
k가 커질수록 정확도는 증가하지만- 비용은 선형 증가, 지연은 병렬화하지 않으면 증가합니다
실무 팁
k=5로 시작해 오프라인 평가로 이득 곡선을 확인- 정답률 상승이 둔화되는 지점에서 멈추기
3) 투표 방법: 단순 다수결 vs 가중치
- 단순 다수결: 구현이 쉽고 강력
- 가중 투표: 각 샘플의
자기 확신 점수또는검증 점수를 반영
주의할 점은 모델의 자기 확신 점수는 종종 잘못 보정(calibration)되어 있다는 것입니다. 가능하면 외부 검증(규칙, 계산기, 타입체크, 스키마 검증)을 가중치에 섞는 편이 안전합니다.
4) 답만 투표할지, 추론까지 검증할지
Self-Consistency는 본질적으로 final answer를 집계합니다. 하지만 실무에서는 다음을 추가하면 더 안정적입니다.
- 중간 산출물(예: 계산 결과, 조건 만족 여부)을 별도 필드로 받기
- 그 필드를 검증해
불량 샘플을 필터링한 뒤 투표
이는 JSON Schema 강제 출력과 궁합이 좋습니다.
구현 예제: Node.js에서 Self-Consistency 투표기
아래 예제는 OpenAI 호환 Chat Completions API 형태로 작성했습니다. 핵심은 k번 호출을 병렬로 실행하고, 최종 답을 정규화(normalize)한 뒤 최빈값을 선택하는 것입니다.
주의: 본문에서 부등호 문자가 그대로 노출되면 MDX에서 빌드 에러가 날 수 있으니, 비교 연산자나 제네릭 표기는 항상 인라인 코드로 감쌌습니다.
import crypto from "node:crypto";
function normalizeAnswer(s) {
return String(s)
.trim()
.toLowerCase()
.replace(/\s+/g, " ");
}
function majorityVote(items) {
const freq = new Map();
for (const it of items) {
const key = normalizeAnswer(it);
freq.set(key, (freq.get(key) ?? 0) + 1);
}
let bestKey = null;
let bestCount = -1;
for (const [k, c] of freq.entries()) {
if (c > bestCount) {
bestKey = k;
bestCount = c;
}
}
return { answer: bestKey, count: bestCount, dist: Object.fromEntries(freq) };
}
async function callLLM({ baseUrl, apiKey, model, messages, temperature }) {
const res = await fetch(`${baseUrl}/v1/chat/completions`, {
method: "POST",
headers: {
"content-type": "application/json",
authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({
model,
messages,
temperature,
top_p: 1,
}),
});
if (!res.ok) {
const text = await res.text();
throw new Error(`LLM error: ${res.status} ${text}`);
}
const data = await res.json();
return data.choices?.[0]?.message?.content ?? "";
}
export async function selfConsistencySolve({
baseUrl,
apiKey,
model,
prompt,
k = 5,
}) {
const messages = [
{
role: "system",
content:
"You are a careful problem solver. Provide only the final answer on the last line as: FINAL: <answer>.",
},
{ role: "user", content: prompt },
];
// 다양성 확보를 위해 temperature를 약간 흔듭니다.
const temps = Array.from({ length: k }, (_, i) => 0.3 + i * 0.15);
const runs = temps.map((t) =>
callLLM({ baseUrl, apiKey, model, messages, temperature: t })
);
const outputs = await Promise.all(runs);
const finals = outputs.map((o) => {
const m = o.match(/FINAL:\s*(.*)\s*$/m);
return m ? m[1] : o;
});
const voted = majorityVote(finals);
return {
voted,
samples: finals,
raw: outputs,
traceId: crypto.randomUUID(),
};
}
이 구현은 단순하지만, 실무에서는 다음 보강이 필요합니다.
FINAL:패턴이 깨질 때를 대비한 폴백- 답 정규화 강화(숫자 포맷, 단위, 소수점 처리)
- 타임아웃/재시도
- 비용 통제를 위한 조건부 실행
구조화 출력과 결합: SC의 품질을 더 끌어올리기
Self-Consistency의 약점은 답 문자열만 투표하면, 형식이 제각각이거나 애매한 답(예: 근사치, 표현 차이)이 섞여 집계가 흔들릴 수 있다는 점입니다. 이를 줄이는 좋은 방법이 JSON Schema 강제 출력입니다.
예를 들어 수학 문제라면 아래처럼 스키마를 강제해 answer를 숫자로 받고, unit이나 assumptions를 분리할 수 있습니다.
{
"type": "object",
"additionalProperties": false,
"properties": {
"answer": { "type": "number" },
"unit": { "type": "string" },
"confidence": { "type": "number", "minimum": 0, "maximum": 1 }
},
"required": ["answer", "unit"]
}
이렇게 만들면 SC 파이프라인이 다음처럼 바뀝니다.
k번 샘플링- JSON 파싱 실패 샘플 제거
answer숫자에 대해 근접 클러스터링 후 투표(예: 반올림, 허용 오차)unit불일치 시 폐기 또는 재질문
스키마 강제 출력은 RAG 환경에서도 환각을 줄이는 데 도움이 됩니다. 자세한 패턴은 RAG 환각을 줄이는 JSON Schema 강제 출력법에서 더 깊게 다뤘습니다.
또한 도구 호출 기반으로 JSON을 주고받을 때 스키마 불일치로 터지는 경우가 많은데, 이 경우는 Claude Tool Use JSON 스키마 불일치 오류 해결 글의 체크리스트가 실전에서 유용합니다.
조건부 Self-Consistency: 비용과 지연을 통제하는 패턴
SC를 항상 적용하면 호출 수가 k배가 되므로, 보통은 다음과 같은 게이트를 둡니다.
1) 1차 결과 검증 후 실패 시 SC
- 단일 호출로 먼저 답을 받는다
- 스키마 검증, 규칙 검증, 간단한 재계산으로 통과 여부 판단
- 실패하면 SC 실행
2) 불확실성 문구 감지 후 SC
모델이 확실하지 않다, 추측 같은 표현을 하면 SC를 켭니다. 다만 이 방법은 모델 성향에 따라 신뢰도가 들쭉날쭉하므로, 가능하면 형식 검증과 같이 쓰는 편이 안전합니다.
3) 난이도 분류 후 SC
프롬프트를 먼저 분류해 논리/수학/제약 충족 유형일 때만 SC를 적용합니다.
실무에서 자주 하는 실수
1) temperature를 너무 낮게 둔다
Self-Consistency는 다양한 경로가 필요합니다. temperature가 너무 낮으면 k번 호출해도 거의 같은 답이 반복되어 이득이 작습니다.
- 권장 시작점:
temperature를0.3에서0.9사이로 분산 - 다만 창의성이 과해져 제약 위반이 늘면 스키마/검증으로 필터링
2) 정규화 없이 문자열 그대로 투표한다
1, 1.0, 01, 약 1은 같은 답일 수 있습니다. 도메인별 정규화가 없으면 표가 갈려서 오히려 품질이 떨어질 수 있습니다.
3) SC로 환각이 자동 해결된다고 믿는다
SC는 정답으로 수렴하는 경향이 있을 때 강합니다. 근거가 부족한 질문(정보 부재)이나 RAG 검색이 실패한 상황에서는, 여러 번 샘플링해도 환각이 일관되게 나올 수 있습니다. 이 경우는 검색 품질, 출처 강제, 스키마 강제 같은 다른 장치가 필요합니다.
확장 아이디어: 투표 대신 랭킹(Verifier) 결합
Self-Consistency의 다음 단계는 생성 모델과 검증 모델을 분리하는 것입니다.
- 생성: 다양한 후보 답을 만든다
- 검증: 규칙/테스트/별도 모델로 후보를 채점한다
- 선택: 최고 점수 답을 채택한다
예를 들어 코드 생성이라면 유닛 테스트 실행 결과를 점수로 쓰고, 수학 문제라면 계산기/심볼릭 툴로 검증할 수 있습니다. 이 패턴은 SC보다 비용이 들 수 있지만, 특정 도메인에서는 훨씬 안정적입니다.
정리
- CoT는 강력하지만 단일 경로 추론이라
초기 실수와분기 고착에 취약합니다. - Self-Consistency는 동일 문제를 여러 번 샘플링해
가장 일관된 답을 선택하는 방식으로, 모델 변경 없이 정확도를 올릴 수 있습니다. - 실무에서는
조건부 SC와구조화 출력(JSON Schema)을 결합해 비용/지연을 통제하면서 품질을 끌어올리는 구성이 효과적입니다.
다음 액션으로는, 현재 서비스의 실패 케이스를 모아 SC 적용 전후를 오프라인으로 비교해 보세요. 특히 형식 검증 실패, 제약 위반, 수학/논리 문제에서 개선 폭이 크게 나오는 경우가 많습니다.