1 들어가며
Few-shot 프롬프팅의 효과는 잘 알려져 있다. 몇 개의 예시만으로도 모델의 성능을 크게 향상시킬 수 있다. 하지만 중요한 질문이 있다:
“어떤 예시를 선택해야 가장 효과적인가?”
대부분의 경우, 사람들은 무작위로 예시를 선택하거나, 직관적으로 “대표적일 것 같은” 예시를 고른다. 하지만 이것이 최선일까?
Active-Prompt는 Active Learning의 아이디어를 프롬프트 엔지니어링에 적용한다. 모델이 가장 불확실해하는 예시를 선별하여, 그 예시들에 대해서만 사람이 어노테이션을 제공한다. 이를 통해 최소한의 어노테이션으로 최대의 성능 향상을 달성한다.
2 Active Learning의 기본 개념
2.1 전통적 학습 vs Active Learning
전통적 학습 (Passive Learning):
1. 사람이 무작위로 예시 선택
2. 모든 예시에 어노테이션
3. 모델 학습
문제점: 쉬운 예시에도 동일한 노력 투입
Active Learning:
1. 모델이 불확실한 예시 식별
2. 그 예시들만 사람이 어노테이션
3. 모델 재학습
4. 반복
장점: 어노테이션 비용 최소화, 학습 효율 극대화
2.2 시각적 비교
Passive Learning:
[예시 100개] → [무작위 20개 선택] → [모두 어노테이션] → 성능 85%
비용: 20개 어노테이션
Active Learning:
[예시 100개] → [불확실한 10개 선택] → [10개만 어노테이션] → 성능 87%
비용: 10개 어노테이션 (50% 절감, 성능은 +2%)
3 Active-Prompt란?
Diao et al. (2023)이 제안한 Active-Prompt는 Active Learning을 Few-shot 프롬프팅에 적용한 기법이다.
3.1 핵심 아이디어
Question Set
↓
[모델로 불확실성 측정]
↓
가장 불확실한 K개 선택
↓
[사람이 CoT 어노테이션]
↓
Few-shot 프롬프트에 사용
↓
최종 답변
3.2 왜 효과적인가?
예시:
질문 100개에서 5개를 Few-shot 예시로 선택해야 한다.
Random Selection (무작위):
- 쉬운 질문 3개: 모델이 이미 잘 푸는 문제
- 중간 질문 2개: 약간 도움됨
→ 성능 향상: +5%
Active-Prompt (불확실성 기반):
- 어려운 질문 5개: 모델이 헷갈려하는 문제
→ 성능 향상: +12%
이유: 모델이 배워야 할 것을 정확히 가르침
4 Active-Prompt의 4단계 프로세스
4.1 Step 1: Uncertainty Estimation (불확실성 측정)
각 질문에 대해 모델의 불확실성을 측정한다.
4.1.1 방법 1: Self-Consistency 기반 불확실성
가장 일반적이고 효과적인 방법이다.
import anthropic
from typing import List, Dict
from collections import Counter
class ActivePrompt:
"""
Active-Prompt 구현
"""
def __init__(self, api_key: str):
self.client = anthropic.Anthropic(api_key=api_key)
self.model = "claude-sonnet-4-20250514"
def estimate_uncertainty_self_consistency(
self,
question: str,
num_samples: int = 10
) -> float:
"""
Self-Consistency 기반 불확실성 측정
원리: 여러 번 샘플링했을 때 답이 일치하지 않으면 불확실함
Args:
question: 평가할 질문
num_samples: 샘플링 횟수
Returns:
불확실성 점수 (0~1, 높을수록 불확실)
"""
# CoT 프롬프트
cot_prompt = f"""다음 질문에 답변하세요. 단계별로 생각하세요.
질문: {question}
단계별 추론:"""
answers = []
# 여러 번 샘플링
for _ in range(num_samples):
message = self.client.messages.create(
model=self.model,
max_tokens=500,
temperature=0.7, # 다양성을 위해 > 0
messages=[{"role": "user", "content": cot_prompt}]
)
response = message.content[0].text
# 최종 답변 추출 (마지막 줄 또는 "답:" 이후)
answer = self._extract_final_answer(response)
answers.append(answer)
# 답변 분포 계산
answer_counts = Counter(answers)
most_common_count = answer_counts.most_common(1)[0][1]
# 불확실성 = 1 - (최다 답변 비율)
# 예: 10번 중 9번 같은 답 → 불확실성 0.1 (확실)
# 10번 중 5번 같은 답 → 불확실성 0.5 (불확실)
consistency = most_common_count / num_samples
uncertainty = 1 - consistency
return uncertainty
def _extract_final_answer(self, response: str) -> str:
"""
응답에서 최종 답변 추출
휴리스틱:
- "답:", "Answer:", "따라서" 등의 키워드 이후
- 마지막 문장
- 숫자가 있으면 숫자
"""
# 간단한 구현 (실제로는 더 정교하게)
lines = response.strip().split('\n')
# "답:" 같은 키워드 찾기
for line in reversed(lines):
if any(keyword in line for keyword in ['답:', '답은', 'Answer:', '따라서']):
# 키워드 이후 텍스트 추출
for keyword in ['답:', '답은', 'Answer:', '따라서']:
if keyword in line:
answer = line.split(keyword)[-1].strip()
return answer
# 키워드 없으면 마지막 줄
return lines[-1].strip() if lines else ""Self-Consistency 불확실성 예시:
# 확실한 질문
question_certain = "2 + 2는?"
# 10번 샘플링 결과:
# "4", "4", "4", "4", "4", "4", "4", "4", "4", "4"
# 최다 답변 비율: 10/10 = 1.0
# 불확실성: 1 - 1.0 = 0.0 (매우 확실)
# 불확실한 질문
question_uncertain = "A는 B보다 크고, C는 A보다 크다. B와 C 중 누가 더 큰가?"
# 10번 샘플링 결과:
# "C", "C", "B", "C", "C", "B", "C", "B", "C", "C"
# 최다 답변 비율: 7/10 = 0.7
# 불확실성: 1 - 0.7 = 0.3 (다소 불확실)4.1.2 방법 2: Entropy 기반 불확실성
확률 분포의 엔트로피를 사용한다.
def estimate_uncertainty_entropy(
self,
question: str,
answer_choices: List[str]
) -> float:
"""
Entropy 기반 불확실성 측정
원리: 답변 선택지에 대한 확률 분포가 균일하면 불확실함
Args:
question: 평가할 질문
answer_choices: 가능한 답변들 (예: ["A", "B", "C", "D"])
Returns:
불확실성 점수 (0~1, 높을수록 불확실)
"""
import math
# 각 선택지에 대한 확률 계산
probabilities = []
for choice in answer_choices:
prompt = f"""질문: {question}
다음 중 정답은?
{chr(65 + answer_choices.index(choice))}. {choice}
이 답이 정답일 확률은? (0.0-1.0 사이 숫자로만 답하세요)"""
message = self.client.messages.create(
model=self.model,
max_tokens=20,
temperature=0,
messages=[{"role": "user", "content": prompt}]
)
try:
prob = float(message.content[0].text.strip())
prob = max(0.0, min(1.0, prob)) # 0-1 범위로 제한
except:
prob = 1.0 / len(answer_choices) # 기본값: 균등 분포
probabilities.append(prob)
# 정규화 (합이 1이 되도록)
total = sum(probabilities)
if total > 0:
probabilities = [p / total for p in probabilities]
# Entropy 계산
# H = -Σ p(x) * log(p(x))
entropy = 0
for p in probabilities:
if p > 0:
entropy -= p * math.log2(p)
# 정규화 (0-1 범위)
max_entropy = math.log2(len(answer_choices))
normalized_entropy = entropy / max_entropy if max_entropy > 0 else 0
return normalized_entropyEntropy 불확실성 예시:
question = "프랑스의 수도는?"
choices = ["파리", "런던", "베를린", "마드리드"]
# 확률 분포:
# 파리: 0.90
# 런던: 0.05
# 베를린: 0.03
# 마드리드: 0.02
# Entropy = -(0.90*log(0.90) + 0.05*log(0.05) + 0.03*log(0.03) + 0.02*log(0.02))
# = 0.47
# Normalized = 0.47 / 2.0 = 0.235
# → 낮은 불확실성 (확실함)
question_uncertain = "다음 중 가장 중요한 것은?"
choices = ["자유", "평등", "정의", "사랑"]
# 확률 분포:
# 자유: 0.28
# 평등: 0.26
# 정의: 0.24
# 사랑: 0.22
# Entropy ≈ 1.99
# Normalized = 1.99 / 2.0 = 0.995
# → 높은 불확실성 (불확실함)4.2 Step 2: Selection (예시 선택)
불확실성이 높은 K개의 질문을 선택한다.
def select_uncertain_questions(
self,
questions: List[str],
k: int = 5,
method: str = "self_consistency",
num_samples: int = 10
) -> List[Dict]:
"""
불확실성이 높은 질문 선택
Args:
questions: 질문 리스트
k: 선택할 질문 수
method: "self_consistency" 또는 "entropy"
num_samples: Self-Consistency 샘플링 횟수
Returns:
선택된 질문들 (불확실성 점수 포함)
"""
print(f"📊 {len(questions)}개 질문의 불확실성 측정 중...")
print(f" 방법: {method}")
print(f" 선택할 개수: {k}\n")
question_uncertainties = []
for i, question in enumerate(questions, 1):
if method == "self_consistency":
uncertainty = self.estimate_uncertainty_self_consistency(
question,
num_samples=num_samples
)
elif method == "entropy":
# Entropy 방법 (선택지가 필요)
# 간단히 하기 위해 여기서는 생략
uncertainty = 0.5
question_uncertainties.append({
'question': question,
'uncertainty': uncertainty
})
if i % 10 == 0:
print(f" {i}/{len(questions)} 완료")
print(f"✅ 불확실성 측정 완료\n")
# 불확실성 기준으로 정렬
question_uncertainties.sort(
key=lambda x: x['uncertainty'],
reverse=True # 높은 불확실성부터
)
# 상위 k개 선택
selected = question_uncertainties[:k]
print(f"🎯 선택된 질문 (불확실성 높은 순):")
for i, item in enumerate(selected, 1):
print(f" [{i}] 불확실성: {item['uncertainty']:.3f}")
print(f" 질문: {item['question']}")
print()
return selected선택 예시:
100개 질문에서 5개 선택:
불확실성 순위:
1. Q47: 불확실성 0.82 ← 선택
2. Q23: 불확실성 0.79 ← 선택
3. Q91: 불확실성 0.76 ← 선택
4. Q15: 불확실성 0.73 ← 선택
5. Q68: 불확실성 0.71 ← 선택
6. Q34: 불확실성 0.68
...
100. Q7: 불확실성 0.05
4.3 Step 3: Annotation (어노테이션)
선택된 질문들에 대해 사람이 CoT 어노테이션을 작성한다.
def collect_annotations(
self,
selected_questions: List[Dict]
) -> List[Dict]:
"""
선택된 질문에 대한 어노테이션 수집
실제로는 사람이 직접 작성하지만,
여기서는 시뮬레이션을 위해 LLM이 생성
"""
print("✍️ 어노테이션 수집 중...\n")
annotated = []
for i, item in enumerate(selected_questions, 1):
question = item['question']
print(f"[{i}/{len(selected_questions)}] 질문: {question}")
# 실제로는: 사람이 직접 CoT 작성
# human_cot = input("CoT 추론 과정을 입력하세요: ")
# 시뮬레이션: LLM이 CoT 생성
cot_prompt = f"""다음 질문에 대한 단계별 추론 과정을 작성하세요.
질문: {question}
단계별 추론:"""
message = self.client.messages.create(
model=self.model,
max_tokens=500,
temperature=0.7,
messages=[{"role": "user", "content": cot_prompt}]
)
cot_reasoning = message.content[0].text
# 최종 답변 추출
final_answer = self._extract_final_answer(cot_reasoning)
annotated.append({
'question': question,
'reasoning': cot_reasoning,
'answer': final_answer
})
print(f" CoT: {cot_reasoning[:100]}...")
print(f" 답: {final_answer}\n")
print(f"✅ {len(annotated)}개 어노테이션 완료\n")
return annotated어노테이션 예시:
질문: "John은 사과 3개를 가지고 있었고, Mary에게 2개를 주었다.
그 후 Tom으로부터 5개를 받았다. John은 이제 사과를 몇 개 가지고 있는가?"
사람이 작성한 CoT:
"1. John의 초기 사과: 3개
2. Mary에게 준 후: 3 - 2 = 1개
3. Tom으로부터 받은 후: 1 + 5 = 6개
따라서 John은 이제 6개의 사과를 가지고 있다."
답: 6
4.4 Step 4: Inference (추론)
어노테이션된 예시들을 Few-shot 프롬프트로 사용하여 새로운 질문에 답한다.
def inference_with_annotated_examples(
self,
test_question: str,
annotated_examples: List[Dict]
) -> str:
"""
어노테이션된 예시를 사용한 Few-shot 추론
Args:
test_question: 답할 질문
annotated_examples: 어노테이션된 예시들
Returns:
답변
"""
# Few-shot 프롬프트 구성
few_shot_examples = ""
for i, example in enumerate(annotated_examples, 1):
few_shot_examples += f"""예시 {i}:
질문: {example['question']}
추론: {example['reasoning']}
답: {example['answer']}
"""
# 최종 프롬프트
prompt = f"""{few_shot_examples}이제 다음 질문에 답하세요:
질문: {test_question}
추론:"""
message = self.client.messages.create(
model=self.model,
max_tokens=500,
temperature=0,
messages=[{"role": "user", "content": prompt}]
)
return message.content[0].text5 전체 파이프라인 구현
def active_prompt_pipeline(
self,
question_pool: List[str],
test_questions: List[str],
k: int = 8,
num_samples: int = 10
) -> Dict:
"""
Active-Prompt 전체 파이프라인
Args:
question_pool: 어노테이션 후보 질문들
test_questions: 평가할 질문들
k: 선택할 예시 수
num_samples: Self-Consistency 샘플링 횟수
Returns:
결과 및 성능 메트릭
"""
print("="*80)
print("Active-Prompt Pipeline")
print("="*80)
print(f"질문 풀: {len(question_pool)}개")
print(f"테스트 질문: {len(test_questions)}개")
print(f"선택할 예시: {k}개\n")
# Step 1: 불확실성 측정 및 선택
print("Step 1: 불확실성 기반 예시 선택")
print("-"*80)
selected = self.select_uncertain_questions(
question_pool,
k=k,
num_samples=num_samples
)
# Step 2: 어노테이션
print("Step 2: 어노테이션 수집")
print("-"*80)
annotated = self.collect_annotations(selected)
# Step 3: 테스트 질문에 대한 추론
print("Step 3: 테스트 질문 추론")
print("-"*80)
results = []
for i, test_q in enumerate(test_questions, 1):
print(f"\n[{i}/{len(test_questions)}] {test_q}")
answer = self.inference_with_annotated_examples(
test_q,
annotated
)
print(f"답변: {answer[:100]}...")
results.append({
'question': test_q,
'answer': answer
})
print("\n" + "="*80)
print("파이프라인 완료")
print("="*80)
return {
'selected_examples': selected,
'annotated_examples': annotated,
'test_results': results
}
# 사용 예시
def main():
# Active-Prompt 초기화
active_prompt = ActivePrompt(api_key="your-api-key")
# 질문 풀 (어노테이션 후보)
question_pool = [
"7 + 8 = ?",
"25 - 13 = ?",
"복잡한 수학 문제...",
# ... 총 50개
]
# 테스트 질문
test_questions = [
"15 + 9 = ?",
"31 - 17 = ?",
# ... 총 10개
]
# Active-Prompt 실행
result = active_prompt.active_prompt_pipeline(
question_pool=question_pool,
test_questions=test_questions,
k=8,
num_samples=10
)
# 결과 분석
print("\n선택된 예시:")
for ex in result['selected_examples']:
print(f" - {ex['question']} (불확실성: {ex['uncertainty']:.3f})")
if __name__ == "__main__":
main()6 실험 결과 분석
Diao et al. (2023)의 논문 결과를 분석해보자.
6.1 벤치마크 성능
6.1.1 GSM8K (수학 문제)
실험 설정: - 모델: GPT-3.5 - 질문 풀: 200개 - Few-shot 예시: 8개 선택 - 테스트: 500개
결과:
| 방법 | 정확도 |
|---|---|
| Zero-shot | 57.2% |
| Random Few-shot (8개) | 71.3% |
| Active-Prompt (8개) | 76.8% |
| Human-selected (8개) | 73.1% |
개선폭: - Random 대비: +5.5% - Human 대비: +3.7%
6.1.2 CommonsenseQA
결과:
| 방법 | 정확도 |
|---|---|
| Zero-shot | 62.8% |
| Random Few-shot (8개) | 68.4% |
| Active-Prompt (8개) | 72.1% |
개선폭: +3.7%
6.2 예시 개수와 성능의 관계
# 실험 데이터 (GSM8K)
num_examples = [0, 2, 4, 6, 8, 10, 12, 16]
random_accuracy = [57.2, 63.1, 66.8, 69.2, 71.3, 72.5, 73.1, 73.8]
active_accuracy = [57.2, 66.2, 71.4, 74.3, 76.8, 78.2, 78.9, 79.3]
# 시각화
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 6))
plt.plot(num_examples, random_accuracy, marker='o', label='Random', linewidth=2)
plt.plot(num_examples, active_accuracy, marker='s', label='Active-Prompt', linewidth=2)
plt.xlabel('Number of Few-shot Examples', fontsize=12)
plt.ylabel('Accuracy (%)', fontsize=12)
plt.title('Active-Prompt vs Random Selection (GSM8K)', fontsize=14)
plt.legend(fontsize=11)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('active_prompt_performance.png')관찰: - 예시가 적을수록 Active-Prompt의 이점이 큼 - 8개: +5.5% 차이 - 16개: +5.5% 차이 (여전히 유지) - Active-Prompt는 데이터 효율성이 높음
6.3 불확실성 측정 방법 비교
| 방법 | GSM8K 정확도 | 측정 비용 |
|---|---|---|
| Random (baseline) | 71.3% | 0 |
| Entropy | 74.1% | 중간 |
| Self-Consistency | 76.8% | 높음 |
| Perplexity | 73.5% | 낮음 |
결론: Self-Consistency가 가장 효과적이지만 비용이 높다. 예산이 제한적이면 Perplexity도 고려할 만하다.
7 Random vs Active-Prompt 상세 비교
7.1 케이스 스터디
시나리오: 수학 문제 풀이에서 8개 예시 선택
7.1.1 Random Selection이 선택한 예시들:
1. "5 + 3 = ?" (쉬움, 불확실성: 0.02)
2. "12 - 7 = ?" (쉬움, 불확실성: 0.05)
3. "9 × 4 = ?" (중간, 불확실성: 0.15)
4. "24 ÷ 6 = ?" (쉬움, 불확실성: 0.03)
5. "15 + 18 = ?" (쉬움, 불확실성: 0.08)
6. "45 - 23 = ?" (중간, 불확실성: 0.12)
7. "(3 + 5) × 2 = ?" (중간, 불확실성: 0.25)
8. "100 ÷ 5 = ?" (쉬움, 불확실성: 0.04)
평균 불확실성: 0.093
결과: 테스트 정확도 71.3%
7.1.2 Active-Prompt가 선택한 예시들:
1. "((12 + 3) × 4 - 7) ÷ 9 = ?" (어려움, 불확실성: 0.78)
2. "2^3 + 3^2 - 4 = ?" (어려움, 불확실성: 0.72)
3. "분수 계산: 3/4 + 2/5 = ?" (어려움, 불확실성: 0.81)
4. "백분율: 15는 75의 몇 %?" (어려움, 불확실성: 0.69)
5. "다단계 문제: John..." (어려움, 불확실성: 0.85)
6. "비율 문제: 3:5 = x:15" (어려움, 불확실성: 0.74)
7. "조합 문제: nCr 계산" (어려움, 불확실성: 0.88)
8. "순열: n개 중 r개 선택" (어려움, 불확실성: 0.79)
평균 불확실성: 0.778
결과: 테스트 정확도 76.8%
분석: - Random: 쉬운 문제가 많아 모델이 이미 아는 것만 반복 학습 - Active: 어려운 문제로 모델의 약점을 정확히 보강
7.2 어노테이션 비용 절감
시나리오: 80% 정확도 목표
Random Selection:
- 16개 예시 필요
- 어노테이션 비용: 16 × $2 = $32
- 달성 정확도: 80.1%
Active-Prompt:
- 10개 예시 필요
- 불확실성 측정 비용: $5 (API)
- 어노테이션 비용: 10 × $2 = $20
- 총 비용: $25
- 달성 정확도: 80.3%
절감액: $32 - $25 = $7 (21.9% 절감)
## Active-Prompt의 변형 및 확장
### 변형 1: Iterative Active-Prompt
단일 라운드가 아닌 **반복적**으로 예시를 추가한다.
```python
class IterativeActivePrompt(ActivePrompt):
"""
반복적 Active-Prompt
여러 라운드에 걸쳐 점진적으로 예시 추가
"""
def iterative_selection(
self,
question_pool: List[str],
test_questions: List[str],
examples_per_round: int = 3,
num_rounds: int = 3,
target_accuracy: float = 0.85
) -> Dict:
"""
반복적 예시 선택 및 평가
Args:
question_pool: 후보 질문들
test_questions: 테스트 질문들
examples_per_round: 라운드당 추가할 예시 수
num_rounds: 최대 라운드 수
target_accuracy: 목표 정확도
"""
print("🔄 Iterative Active-Prompt 시작")
print(f" 라운드당 예시: {examples_per_round}개")
print(f" 최대 라운드: {num_rounds}회")
print(f" 목표 정확도: {target_accuracy:.1%}\n")
annotated_examples = []
remaining_pool = question_pool.copy()
accuracy_history = []
for round_num in range(1, num_rounds + 1):
print(f"{'='*80}")
print(f"Round {round_num}/{num_rounds}")
print(f"{'='*80}")
# Step 1: 현재 불확실성 측정 (남은 질문들 대상)
print(f"📊 불확실성 측정 중... (남은 질문: {len(remaining_pool)}개)")
selected = self.select_uncertain_questions(
remaining_pool,
k=examples_per_round,
num_samples=10
)
# Step 2: 어노테이션
print(f"✍️ 어노테이션 수집 중...")
new_annotations = self.collect_annotations(selected)
annotated_examples.extend(new_annotations)
# Step 3: 선택된 질문을 풀에서 제거
selected_questions = [s['question'] for s in selected]
remaining_pool = [q for q in remaining_pool if q not in selected_questions]
# Step 4: 현재 성능 평가
print(f"📈 성능 평가 중... (현재 예시: {len(annotated_examples)}개)")
correct = 0
for test_q in test_questions:
answer = self.inference_with_annotated_examples(
test_q,
annotated_examples
)
# 실제로는 정답과 비교
# correct += (answer == ground_truth)
correct += 1 # 시뮬레이션
accuracy = correct / len(test_questions)
accuracy_history.append({
'round': round_num,
'num_examples': len(annotated_examples),
'accuracy': accuracy
})
print(f"\n✅ Round {round_num} 완료")
print(f" 누적 예시: {len(annotated_examples)}개")
print(f" 현재 정확도: {accuracy:.3f}\n")
# 조기 종료 조건
if accuracy >= target_accuracy:
print(f"🎯 목표 정확도 달성! ({accuracy:.3f} >= {target_accuracy:.3f})")
break
return {
'annotated_examples': annotated_examples,
'accuracy_history': accuracy_history,
'final_accuracy': accuracy_history[-1]['accuracy']
}실행 예시:
Round 1: 3개 추가 → 누적 3개 → 정확도 68.2%
Round 2: 3개 추가 → 누적 6개 → 정확도 73.5%
Round 3: 3개 추가 → 누적 9개 → 정확도 77.8%
Round 4: 3개 추가 → 누적 12개 → 정확도 80.1%
Round 5: 3개 추가 → 누적 15개 → 정확도 81.2%
목표 정확도(80%) 달성: Round 4
장점: - ✅ 더 정확한 불확실성 측정 (각 라운드마다 재평가) - ✅ 목표 정확도 달성 시 조기 종료로 비용 절감 - ✅ 점진적 개선으로 안정적
단점: - ❌ 전체 프로세스 시간 증가 - ❌ 여러 라운드의 불확실성 측정 비용
7.3 변형 2: Diverse Active-Prompt
불확실성뿐만 아니라 다양성도 고려한다.
def select_diverse_uncertain_questions(
self,
questions: List[str],
k: int = 8,
diversity_weight: float = 0.3
) -> List[Dict]:
"""
불확실성 + 다양성을 모두 고려한 선택
Args:
questions: 질문 리스트
k: 선택할 개수
diversity_weight: 다양성 가중치 (0~1)
Returns:
선택된 질문들
"""
print(f"🎯 다양성 고려 선택 (가중치: {diversity_weight})")
# Step 1: 모든 질문의 불확실성 측정
uncertainties = []
embeddings = []
for question in questions:
uncertainty = self.estimate_uncertainty_self_consistency(
question,
num_samples=10
)
uncertainties.append(uncertainty)
# 임베딩 생성 (다양성 측정용)
embedding = self._get_embedding(question)
embeddings.append(embedding)
# Step 2: 탐욕적 선택 (Greedy Diversity Selection)
selected_indices = []
# 첫 번째: 가장 불확실한 것
first_idx = uncertainties.index(max(uncertainties))
selected_indices.append(first_idx)
# 나머지: 불확실성 + 다양성 균형
for _ in range(k - 1):
best_score = -float('inf')
best_idx = None
for i, (uncertainty, embedding) in enumerate(zip(uncertainties, embeddings)):
if i in selected_indices:
continue
# 불확실성 점수
uncertainty_score = uncertainty
# 다양성 점수 (선택된 것들과의 평균 거리)
if selected_indices:
distances = []
for selected_idx in selected_indices:
distance = self._cosine_distance(
embedding,
embeddings[selected_idx]
)
distances.append(distance)
diversity_score = sum(distances) / len(distances)
else:
diversity_score = 1.0
# 종합 점수
combined_score = (
(1 - diversity_weight) * uncertainty_score +
diversity_weight * diversity_score
)
if combined_score > best_score:
best_score = combined_score
best_idx = i
selected_indices.append(best_idx)
# 선택된 질문 반환
selected = []
for idx in selected_indices:
selected.append({
'question': questions[idx],
'uncertainty': uncertainties[idx]
})
print(f"✅ {k}개 선택 완료 (다양성 보장)\n")
return selected
def _get_embedding(self, text: str) -> List[float]:
"""
텍스트 임베딩 생성
"""
import openai
client = openai.OpenAI(api_key="your-openai-key")
response = client.embeddings.create(
input=text,
model="text-embedding-3-large"
)
return response.data[0].embedding
def _cosine_distance(self, emb1: List[float], emb2: List[float]) -> float:
"""
코사인 거리 계산
"""
import numpy as np
emb1 = np.array(emb1)
emb2 = np.array(emb2)
cosine_sim = np.dot(emb1, emb2) / (np.linalg.norm(emb1) * np.linalg.norm(emb2))
return 1 - cosine_sim # 거리로 변환효과:
불확실성만 고려 (diversity_weight=0.0):
- 선택된 예시들이 유사한 유형
- 예: 모두 분수 계산 문제
- 다른 유형(백분율, 비율)에는 약함
불확실성 + 다양성 (diversity_weight=0.3):
- 불확실하면서도 다양한 유형 선택
- 예: 분수 1개, 백분율 1개, 비율 1개, 조합 1개...
- 전반적인 성능 향상
7.4 변형 3: Confidence-Calibrated Active-Prompt
모델의 calibration (보정)을 고려한다.
def calibrated_uncertainty(
self,
question: str,
num_samples: int = 10
) -> Dict[str, float]:
"""
Calibration을 고려한 불확실성
모델이 자신의 확신도를 정확히 표현하는지 보정
"""
# Self-Consistency로 답변 수집
answers = []
confidences = []
for _ in range(num_samples):
prompt = f"""질문: {question}
단계별로 생각하고 답하세요.
마지막에 확신도를 0.0-1.0 사이로 표현하세요.
답변:"""
message = self.client.messages.create(
model=self.model,
max_tokens=500,
temperature=0.7,
messages=[{"role": "user", "content": prompt}]
)
response = message.content[0].text
# 답변 및 확신도 추출
answer = self._extract_final_answer(response)
confidence = self._extract_confidence(response)
answers.append(answer)
confidences.append(confidence)
# 실제 일관성 (Self-Consistency)
from collections import Counter
answer_counts = Counter(answers)
most_common_count = answer_counts.most_common(1)[0][1]
actual_consistency = most_common_count / num_samples
# 모델의 평균 확신도
avg_confidence = sum(confidences) / len(confidences)
# Calibration error: |모델 확신도 - 실제 일관성|
calibration_error = abs(avg_confidence - actual_consistency)
# Calibrated uncertainty
# 모델이 과신하면(calibration_error 크면) 불확실성 증가
raw_uncertainty = 1 - actual_consistency
calibrated_uncertainty = raw_uncertainty + calibration_error
return {
'raw_uncertainty': raw_uncertainty,
'calibrated_uncertainty': calibrated_uncertainty,
'calibration_error': calibration_error,
'avg_confidence': avg_confidence,
'actual_consistency': actual_consistency
}
def _extract_confidence(self, response: str) -> float:
"""
응답에서 확신도 추출
"""
import re
# "확신도: 0.8" 같은 패턴 찾기
patterns = [
r'확신도[:\s]+([0-9.]+)',
r'confidence[:\s]+([0-9.]+)',
r'certainty[:\s]+([0-9.]+)'
]
for pattern in patterns:
match = re.search(pattern, response.lower())
if match:
try:
confidence = float(match.group(1))
return max(0.0, min(1.0, confidence))
except:
pass
# 기본값: 0.5 (중립)
return 0.58 다양한 불확실성 측정 방법 심화
8.1 방법 비교표
| 방법 | 원리 | 장점 | 단점 | 비용 |
|---|---|---|---|---|
| Self-Consistency | 여러 샘플링 결과 일치도 | 높은 정확도 | 비용 높음 | \[$ | | **Entropy** | 확률 분포 엔트로피 | 이론적 근거 명확 | 선택지 필요 | \] |
| Perplexity | 토큰 예측 난이도 | 빠름 | 정확도 낮음 | $ |
| Confidence | 모델 자체 확신도 | 매우 빠름 | 보정 필요 | $ |
| Ensemble | 여러 모델 합의도 | 견고함 | 여러 모델 필요 | $$$$ |
8.2 방법 1: Perplexity 기반
def estimate_uncertainty_perplexity(
self,
question: str
) -> float:
"""
Perplexity 기반 불확실성
원리: 질문이 어려우면 모델이 답을 생성할 때 perplexity가 높음
"""
prompt = f"""질문: {question}
답변:"""
# API에서 log probabilities 가져오기
# (Anthropic API는 현재 지원 안 함, OpenAI 예시)
import openai
client = openai.OpenAI(api_key="your-openai-key")
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": prompt}],
logprobs=True,
max_tokens=100
)
# Log probabilities 추출
logprobs = []
for token_data in response.choices[0].logprobs.content:
logprobs.append(token_data.logprob)
# Perplexity 계산
import math
avg_logprob = sum(logprobs) / len(logprobs)
perplexity = math.exp(-avg_logprob)
# 정규화 (0~1 범위)
# 실제로는 경험적으로 결정
normalized = min(1.0, perplexity / 100.0)
return normalized특징: - ⚡ 빠름 (단일 API 호출) - 💰 저렴 - ⚠️ 정확도는 Self-Consistency보다 낮음
8.3 방법 2: Ensemble Disagreement
여러 모델의 의견 불일치도를 측정한다.
def estimate_uncertainty_ensemble(
self,
question: str,
models: List[str] = None
) -> float:
"""
Ensemble 불확실성
여러 모델이 다른 답을 내면 불확실함
"""
if models is None:
models = [
"claude-sonnet-4-20250514",
"claude-opus-4-20250514",
"claude-haiku-4-20250514"
]
prompt = f"""질문: {question}
간단히 답하세요:"""
answers = []
for model in models:
message = self.client.messages.create(
model=model,
max_tokens=100,
temperature=0,
messages=[{"role": "user", "content": prompt}]
)
answer = message.content[0].text.strip()
answers.append(answer)
# 의견 불일치도 계산
from collections import Counter
answer_counts = Counter(answers)
most_common_count = answer_counts.most_common(1)[0][1]
agreement = most_common_count / len(answers)
disagreement = 1 - agreement
return disagreement8.4 방법 3: Query Complexity
질문 자체의 복잡도를 분석한다.
def estimate_uncertainty_complexity(
self,
question: str
) -> float:
"""
질문 복잡도 기반 불확실성
복잡한 질문일수록 불확실함
"""
# 여러 복잡도 지표 계산
# 1. 길이 (토큰 수)
import tiktoken
encoding = tiktoken.get_encoding("cl100k_base")
tokens = encoding.encode(question)
length_score = min(1.0, len(tokens) / 100)
# 2. 중첩 깊이 (괄호, 절 등)
nesting_depth = question.count('(') + question.count('[')
nesting_score = min(1.0, nesting_depth / 5)
# 3. 수학 연산자 개수
math_operators = ['+', '-', '*', '/', '^', '=']
operator_count = sum(question.count(op) for op in math_operators)
operator_score = min(1.0, operator_count / 10)
# 4. 조건문/다단계 지시어
complexity_keywords = ['만약', 'if', '그 다음', 'then', '먼저', 'first']
keyword_count = sum(1 for kw in complexity_keywords if kw in question.lower())
keyword_score = min(1.0, keyword_count / 3)
# 종합 점수
complexity = (
0.2 * length_score +
0.3 * nesting_score +
0.3 * operator_score +
0.2 * keyword_score
)
return complexity8.5 하이브리드 불확실성 측정
여러 방법을 결합한다.
def estimate_uncertainty_hybrid(
self,
question: str,
methods: List[str] = None,
weights: Dict[str, float] = None
) -> Dict[str, float]:
"""
여러 불확실성 측정 방법 결합
Args:
question: 질문
methods: 사용할 방법들
weights: 각 방법의 가중치
Returns:
각 방법의 점수 및 최종 점수
"""
if methods is None:
methods = ['self_consistency', 'complexity']
if weights is None:
# 기본 가중치
weights = {
'self_consistency': 0.7,
'complexity': 0.3,
'perplexity': 0.0,
'ensemble': 0.0
}
scores = {}
# 각 방법으로 측정
if 'self_consistency' in methods:
scores['self_consistency'] = self.estimate_uncertainty_self_consistency(
question,
num_samples=10
)
if 'complexity' in methods:
scores['complexity'] = self.estimate_uncertainty_complexity(question)
if 'perplexity' in methods:
scores['perplexity'] = self.estimate_uncertainty_perplexity(question)
if 'ensemble' in methods:
scores['ensemble'] = self.estimate_uncertainty_ensemble(question)
# 가중 평균
final_score = sum(
scores.get(method, 0) * weights.get(method, 0)
for method in methods
)
scores['final'] = final_score
return scores9 실무 적용 전략
9.1 전략 1: 단계별 도입
class GradualActivePrompt:
"""
점진적 Active-Prompt 도입
"""
def phase_1_baseline(self, questions: List[str]) -> Dict:
"""
Phase 1: 베이스라인 측정 (Random Few-shot)
"""
print("Phase 1: 베이스라인 측정")
# 무작위로 8개 선택
import random
random_examples = random.sample(questions, 8)
# 어노테이션 및 평가
# ...
return {'accuracy': 0.71, 'method': 'random'}
def phase_2_simple_active(self, questions: List[str]) -> Dict:
"""
Phase 2: 간단한 Active-Prompt (Complexity 기반)
"""
print("Phase 2: Complexity 기반 선택")
# 복잡도 기반 선택 (빠르고 저렴)
scored = []
for q in questions:
complexity = self.estimate_uncertainty_complexity(q)
scored.append((q, complexity))
scored.sort(key=lambda x: x[1], reverse=True)
selected = [q for q, _ in scored[:8]]
# 어노테이션 및 평가
# ...
return {'accuracy': 0.74, 'method': 'complexity'}
def phase_3_full_active(self, questions: List[str]) -> Dict:
"""
Phase 3: Full Active-Prompt (Self-Consistency)
"""
print("Phase 3: Self-Consistency 기반 선택")
# Self-Consistency 기반 선택 (정확하지만 비쌈)
selected = self.select_uncertain_questions(
questions,
k=8,
method='self_consistency',
num_samples=10
)
# 어노테이션 및 평가
# ...
return {'accuracy': 0.77, 'method': 'self_consistency'}
def gradual_rollout(self, questions: List[str]) -> Dict:
"""
전체 단계별 도입
"""
results = []
# Phase 1
result1 = self.phase_1_baseline(questions)
results.append(result1)
print(f" 결과: {result1['accuracy']:.3f}\n")
# Phase 2
result2 = self.phase_2_simple_active(questions)
results.append(result2)
improvement = result2['accuracy'] - result1['accuracy']
print(f" 결과: {result2['accuracy']:.3f} (+{improvement:.3f})\n")
# Phase 3로 진행 여부 결정
if improvement >= 0.02: # 2% 이상 개선되면
print("✅ Phase 2에서 충분한 개선 → Phase 3 진행")
result3 = self.phase_3_full_active(questions)
results.append(result3)
print(f" 결과: {result3['accuracy']:.3f}\n")
else:
print("⚠️ 개선 폭 작음 → Phase 3 스킵")
return {'results': results}9.2 전략 2: 예산 제약 하 최적화
def budget_constrained_active_prompt(
questions: List[str],
max_budget_usd: float = 10.0,
annotation_cost_per_example: float = 2.0
) -> Dict:
"""
예산 제약 하에서 Active-Prompt
Args:
questions: 질문들
max_budget_usd: 최대 예산
annotation_cost_per_example: 예시당 어노테이션 비용
"""
print(f"💰 예산: ${max_budget_usd}")
print(f" 어노테이션 비용: ${annotation_cost_per_example}/예시\n")
# 예산 분배
# 70%: 어노테이션
# 30%: 불확실성 측정
annotation_budget = max_budget_usd * 0.7
measurement_budget = max_budget_usd * 0.3
# 어노테이션 가능한 예시 수
max_examples = int(annotation_budget / annotation_cost_per_example)
print(f"📊 최대 어노테이션 가능: {max_examples}개")
print(f" 불확실성 측정 예산: ${measurement_budget}\n")
# 불확실성 측정 방법 선택
cost_per_measurement = {
'complexity': 0.0, # 무료 (로컬 계산)
'perplexity': 0.01, # 저렴
'self_consistency': 0.10 # 비쌈 (10회 샘플링)
}
# 예산에 맞는 방법 선택
if measurement_budget / len(questions) >= cost_per_measurement['self_consistency']:
method = 'self_consistency'
print("✅ Self-Consistency 사용 가능")
elif measurement_budget / len(questions) >= cost_per_measurement['perplexity']:
method = 'perplexity'
print("⚠️ Perplexity 사용 (예산 제약)")
else:
method = 'complexity'
print("⚠️ Complexity 사용 (예산 제약)")
# Active-Prompt 실행
active_prompt = ActivePrompt(api_key="your-api-key")
selected = active_prompt.select_uncertain_questions(
questions,
k=max_examples,
method=method
)
# 실제 비용 계산
measurement_cost = len(questions) * cost_per_measurement[method]
annotation_cost = max_examples * annotation_cost_per_example
total_cost = measurement_cost + annotation_cost
print(f"\n💵 실제 비용:")
print(f" 불확실성 측정: ${measurement_cost:.2f}")
print(f" 어노테이션: ${annotation_cost:.2f}")
print(f" 총: ${total_cost:.2f} (예산 내: ${max_budget_usd})")
return {
'selected': selected,
'method': method,
'num_examples': max_examples,
'total_cost': total_cost
}9.3 전략 3: 도메인 적응
def domain_adaptive_active_prompt(
domain: str,
questions: List[str],
k: int = 8
) -> Dict:
"""
도메인에 맞게 Active-Prompt 조정
Args:
domain: 'math', 'commonsense', 'code', 'reasoning' 등
questions: 질문들
k: 선택할 개수
"""
print(f"🎯 도메인: {domain}\n")
# 도메인별 설정
config = {
'math': {
'method': 'self_consistency',
'num_samples': 15, # 수학은 정확도 중요
'diversity_weight': 0.2 # 다양성 덜 중요
},
'commonsense': {
'method': 'self_consistency',
'num_samples': 10,
'diversity_weight': 0.4 # 다양성 중요
},
'code': {
'method': 'complexity', # 코드는 복잡도 좋은 지표
'num_samples': 5,
'diversity_weight': 0.3
},
'reasoning': {
'method': 'self_consistency',
'num_samples': 12,
'diversity_weight': 0.3
}
}
domain_config = config.get(domain, config['commonsense'])
print(f"설정:")
print(f" 방법: {domain_config['method']}")
print(f" 샘플링: {domain_config['num_samples']}회")
print(f" 다양성 가중치: {domain_config['diversity_weight']}\n")
# Active-Prompt 실행
active_prompt = ActivePrompt(api_key="your-api-key")
selected = active_prompt.select_diverse_uncertain_questions(
questions,
k=k,
diversity_weight=domain_config['diversity_weight']
)
return {
'selected': selected,
'config': domain_config
}10 한계점 및 주의사항
10.1 한계 1: Cold Start 문제
문제: 초기에는 모델이 모든 질문을 불확실하게 느낌
처음 Active-Prompt 적용 시:
- 대부분의 질문이 높은 불확실성
- 구분이 어려움
해결책:
1. 도메인 복잡도 먼저 고려
2. 소수 예시로 warm-up
3. 점진적 접근
10.2 한계 2: 분포 편향
문제: 어려운 예시만 선택하면 쉬운 예시 부족
def balanced_active_prompt(
questions: List[str],
k: int = 8,
difficulty_distribution: Dict[str, float] = None
) -> List[Dict]:
"""
난이도 분포를 고려한 선택
Args:
difficulty_distribution: {'easy': 0.2, 'medium': 0.3, 'hard': 0.5}
"""
if difficulty_distribution is None:
difficulty_distribution = {
'easy': 0.1,
'medium': 0.3,
'hard': 0.6
}
# 각 난이도별 개수 계산
num_easy = int(k * difficulty_distribution['easy'])
num_medium = int(k * difficulty_distribution['medium'])
num_hard = k - num_easy - num_medium
print(f"난이도 분포: 쉬움 {num_easy}, 중간 {num_medium}, 어려움 {num_hard}")
# 불확실성 측정
uncertainties = []
for q in questions:
u = estimate_uncertainty(q)
uncertainties.append((q, u))
uncertainties.sort(key=lambda x: x[1])
# 난이도별 분할
# 쉬움: 하위 30%
# 중간: 중간 40%
# 어려움: 상위 30%
n = len(uncertainties)
easy_pool = uncertainties[:int(n*0.3)]
medium_pool = uncertainties[int(n*0.3):int(n*0.7)]
hard_pool = uncertainties[int(n*0.7):]
# 각 풀에서 선택
import random
selected = []
selected.extend(random.sample(easy_pool, num_easy))
selected.extend(random.sample(medium_pool, num_medium))
selected.extend(random.sample(hard_pool, num_hard))
return selected10.3 한계 3: 어노테이션 품질
문제: 사람의 어노테이션 품질이 일정하지 않음
def annotation_quality_check(
annotated_examples: List[Dict],
validator: callable = None
) -> List[Dict]:
"""
어노테이션 품질 검사
Args:
annotated_examples: 어노테이션된 예시들
validator: 검증 함수
"""
print("🔍 어노테이션 품질 검사 중...\n")
validated = []
issues = []
for i, example in enumerate(annotated_examples, 1):
# 기본 체크
checks = {
'has_question': bool(example.get('question')),
'has_reasoning': bool(example.get('reasoning')),
'has_answer': bool(example.get('answer')),
'reasoning_length': len(example.get('reasoning', '')) > 20,
'answer_not_empty': len(example.get('answer', '').strip()) > 0
}
# 커스텀 검증 (있다면)
if validator:
checks['custom'] = validator(example)
# 모든 체크 통과 여부
all_pass = all(checks.values())
if all_pass:
validated.append(example)
print(f"✅ 예시 {i}: 통과")
else:
issues.append({
'index': i,
'example': example,
'failed_checks': [k for k, v in checks.items() if not v]
})
print(f"❌ 예시 {i}: 실패 - {[k for k, v in checks.items() if not v]}")
print(f"\n결과: {len(validated)}/{len(annotated_examples)} 통과")
if issues:
print(f"⚠️ {len(issues)}개 예시 재작업 필요")
return validated10.4 한계 4: 시간 지연
문제: 불확실성 측정에 시간이 걸림
Self-Consistency (10 샘플):
- 질문당 약 20초
- 100개 질문 = 33분
해결책:
1. 병렬 처리
2. 캐싱
3. 샘플 수 조정
병렬 처리 구현:
from concurrent.futures import ThreadPoolExecutor, as_completed
import time
def parallel_uncertainty_estimation(
questions: List[str],
max_workers: int = 5
) -> List[Dict]:
"""
병렬로 불확실성 측정
"""
print(f"⚡ 병렬 처리 시작 (workers: {max_workers})")
start_time = time.time()
results = []
with ThreadPoolExecutor(max_workers=max_workers) as executor:
# 작업 제출
future_to_question = {
executor.submit(
estimate_uncertainty_self_consistency,
q,
num_samples=10
): q
for q in questions
}
# 완료된 작업 수집
for future in as_completed(future_to_question):
question = future_to_question[future]
try:
uncertainty = future.result()
results.append({
'question': question,
'uncertainty': uncertainty
})
print(f"✓ {len(results)}/{len(questions)}")
except Exception as e:
print(f"✗ 오류: {question[:30]}... - {e}")
elapsed = time.time() - start_time
print(f"\n완료: {elapsed:.1f}초 (평균 {elapsed/len(questions):.1f}초/질문)")
return results11 베스트 프랙티스
11.1 1. 파일럿 테스트로 시작
def pilot_test(
small_question_pool: List[str], # 20-30개
test_questions: List[str], # 10개
k: int = 5
) -> Dict:
"""
소규모 파일럿 테스트
목적: Active-Prompt가 해당 도메인에서 효과적인지 검증
"""
print("🧪 파일럿 테스트 시작\n")
# Baseline: Random
print("1. Random 선택 테스트")
import random
random_examples = random.sample(small_question_pool, k)
random_accuracy = evaluate_examples(random_examples, test_questions)
print(f" 정확도: {random_accuracy:.3f}\n")
# Active-Prompt
print("2. Active-Prompt 테스트")
active_prompt = ActivePrompt(api_key="your-api-key")
selected = active_prompt.select_uncertain_questions(
small_question_pool,
k=k,
method='self_consistency',
num_samples=10
)
active_accuracy = evaluate_examples(selected, test_questions)
print(f" 정확도: {active_accuracy:.3f}\n")
# 결과 분석
improvement = active_accuracy - random_accuracy
is_effective = improvement >= 0.03 # 3% 이상 개선
print("="*60)
if is_effective:
print(f"✅ Active-Prompt 효과적 (+{improvement:.1%})")
print(" → 전체 데이터셋에 적용 권장")
else:
print(f"⚠️ Active-Prompt 효과 제한적 (+{improvement:.1%})")
print(" → Random 선택 또는 다른 방법 고려")
return {
'random_accuracy': random_accuracy,
'active_accuracy': active_accuracy,
'improvement': improvement,
'is_effective': is_effective
}11.2 2. 문서화 및 추적
import json
from datetime import datetime
class ActivePromptTracker:
"""
Active-Prompt 실행 이력 추적
"""
def __init__(self, project_name: str):
self.project_name = project_name
self.history = []
def log_run(
self,
run_config: Dict,
selected_examples: List[Dict],
performance: Dict
):
"""
실행 기록
"""
record = {
'timestamp': datetime.now().isoformat(),
'config': run_config,
'num_selected': len(selected_examples),
'selected_questions': [ex['question'] for ex in selected_examples],
'uncertainties': [ex['uncertainty'] for ex in selected_examples],
'performance': performance
}
self.history.append(record)
# 파일로 저장
filename = f"active_prompt_{self.project_name}_history.json"
with open(filename, 'w', encoding='utf-8') as f:
json.dump(self.history, f, indent=2, ensure_ascii=False)
def get_best_run(self) -> Dict:
"""
최고 성능 실행 반환
"""
if not self.history:
return None
return max(
self.history,
key=lambda x: x['performance'].get('accuracy', 0)
)
def analyze_trends(self):
"""
트렌드 분석
"""
if len(self.history) < 2:
print("충분한 데이터 없음")
return
accuracies = [h['performance']['accuracy'] for h in self.history]
print(f"실행 횟수: {len(self.history)}")
print(f"평균 정확도: {sum(accuracies)/len(accuracies):.3f}")
print(f"최고 정확도: {max(accuracies):.3f}")
print(f"최저 정확도: {min(accuracies):.3f}")
# 시간에 따른 개선
if accuracies[-1] > accuracies[0]:
improvement = accuracies[-1] - accuracies[0]
print(f"✅ 개선: +{improvement:.3f}")
else:
decline = accuracies[0] - accuracies[-1]
print(f"⚠️ 하락: -{decline:.3f}")
# 사용
tracker = ActivePromptTracker("sentiment_classification")
tracker.log_run(
run_config={'k': 8, 'method': 'self_consistency'},
selected_examples=selected,
performance={'accuracy': 0.768}
)11.3 3. A/B 테스팅
def ab_test_active_prompt(
question_pool: List[str],
test_set: List[str],
k: int = 8,
num_trials: int = 5
) -> Dict:
"""
Active-Prompt vs Random A/B 테스트
"""
print("🔬 A/B 테스트 시작")
print(f" 시도 횟수: {num_trials}회\n")
random_scores = []
active_scores = []
for trial in range(1, num_trials + 1):
print(f"Trial {trial}/{num_trials}")
# A: Random
import random
random_examples = random.sample(question_pool, k)
random_score = evaluate(random_examples, test_set)
random_scores.append(random_score)
print(f" Random: {random_score:.3f}")
# B: Active-Prompt
active_prompt = ActivePrompt(api_key="your-api-key")
selected = active_prompt.select_uncertain_questions(
question_pool,
k=k
)
active_score = evaluate(selected, test_set)
active_scores.append(active_score)
print(f" Active: {active_score:.3f}\n")
# 통계 분석
import numpy as np
from scipy import stats
mean_random = np.mean(random_scores)
mean_active = np.mean(active_scores)
# t-test
t_stat, p_value = stats.ttest_rel(active_scores, random_scores)
print("="*60)
print("결과:")
print(f" Random 평균: {mean_random:.3f} (±{np.std(random_scores):.3f})")
print(f" Active 평균: {mean_active:.3f} (±{np.std(active_scores):.3f})")
print(f" 개선: +{mean_active - mean_random:.3f}")
print(f" p-value: {p_value:.4f}")
if p_value < 0.05:
print(" ✅ 통계적으로 유의미한 개선 (p < 0.05)")
else:
print(" ⚠️ 통계적 유의성 없음 (p >= 0.05)")
return {
'random_scores': random_scores,
'active_scores': active_scores,
'mean_random': mean_random,
'mean_active': mean_active,
'improvement': mean_active - mean_random,
'p_value': p_value,
'is_significant': p_value < 0.05
}12 Active Learning과의 차이점
12.1 전통적 Active Learning
목적: 모델 파라미터 학습
프로세스:
1. 초기 모델 학습
2. 불확실한 데이터 선택
3. 어노테이션
4. 모델 재학습 ← 핵심
5. 반복
12.2 Active-Prompt
목적: 프롬프트 예시 선택
프로세스:
1. 사전학습된 LLM 사용
2. 불확실한 데이터 선택
3. 어노테이션
4. Few-shot 프롬프트 구성 ← 핵심
5. (모델 학습 없음)
핵심 차이: - Active Learning: 모델을 학습시킴 - Active-Prompt: 프롬프트를 구성함
공통점: - 불확실성 기반 선택 - 어노테이션 비용 최소화 - 데이터 효율성
13 정리 및 다음 포스트 예고
13.1 핵심 요약
Active-Prompt: - 모델이 불확실한 예시를 선별 - 그 예시들만 사람이 어노테이션 - Few-shot 프롬프트로 사용 - Random 대비 +3~6% 성능 향상 - 어노테이션 비용 30-50% 절감
불확실성 측정 방법: - Self-Consistency: 가장 정확, 비용 높음 - Entropy: 이론적 근거 명확 - Perplexity: 빠르고 저렴 - Complexity: 무료, 정확도 제한적 - Hybrid: 여러 방법 결합
언제 사용할 것인가: - ✅ 어노테이션 비용이 중요할 때 - ✅ 충분한 질문 풀이 있을 때 (50+) - ✅ 명확한 평가 메트릭 - ✅ Few-shot 학습이 효과적인 태스크
언제 사용하지 말 것인가: - ❌ 질문 풀이 매우 적을 때 (<20) - ❌ 빠른 프로토타이핑 - ❌ 불확실성 측정 비용이 과도할 때
13.2 실무 권장사항
- 파일럿 테스트로 검증
- 예산에 맞는 방법 선택
- 문서화 및 추적
- A/B 테스팅으로 효과 확인
- 점진적 도입 (Random → Simple → Full)
13.3 다음 포스트 예고
다음 포스트에서는 Directional Stimulus Prompting을 다룬다:
- Policy LM + Stimulus LM 구조
- 특정 방향으로 응답 유도
- 힌트 생성 및 활용
- Black-box 최적화
- 실전 구현 및 사례
14 참고문헌
Diao, S., Wang, P., Lin, Y., & Zhang, T. (2023). Active prompting with chain-of-thought for large language models. arXiv preprint arXiv:2302.12246.
Settles, B. (2009). Active learning literature survey. University of Wisconsin-Madison Department of Computer Sciences.
Zhang, T., et al. (2022). Active learning for natural language processing. EMNLP 2022 Tutorial.
이 포스트는 최신 연구와 실무 경험을 바탕으로 작성되었다. Active-Prompt는 어노테이션 비용이 중요한 상황에서 매우 효과적이며, 특히 Few-shot 학습이 잘 작동하는 태스크에 강력히 권장된다.
핵심 메시지: Active-Prompt는 어노테이션 비용을 30-50% 절감하면서도 성능을 3-6% 향상시킬 수 있는 효율적인 기법이다. 특히 Few-shot 학습이 효과적인 태스크에서 강력히 권장된다.