이 글은 누구를 위한 것인가
- 학습 데이터가 부족한 특수 도메인 모델을 훈련하려는 팀
- 실제 데이터의 개인정보 문제를 합성 데이터로 해결하려는 개발자
- LLM으로 파인튜닝용 데이터셋을 자동 생성하려는 팀
들어가며
특수 도메인(의료, 법률, 금융) ML 모델은 학습 데이터가 부족하다. 실제 데이터는 개인정보 문제가 있다. LLM으로 합성 데이터를 생성하면 다양하고 제어된 학습 데이터를 무한히 만들 수 있다.
이 글은 bluefoxdev.kr의 LLM 합성 데이터 생성 가이드 를 참고하여 작성했습니다.
1. 합성 데이터 생성 전략
[합성 데이터 활용 시나리오]
분류기 학습: 감성 분석, 의도 분류
NER: 개체명 인식 (도메인 특화)
Q&A: 문서 기반 질문-답변 쌍
대화: 챗봇 학습 데이터
코드: 코드 생성/수정 예시
[다양성 확보 기법]
온도(temperature) 조절: 0.7~1.0
시드 예시 변형: 패러프레이징
페르소나 다양화: 전문가/초보자/다양한 연령대
컨텍스트 변형: 산업, 지역, 시간대
[품질 필터링]
자기 평가: LLM이 생성 데이터 품질 채점
일관성 검사: 레이블과 내용 일치 여부
중복 제거: 문장 임베딩 유사도 (>0.95 제거)
인간 검토: 10% 랜덤 샘플링
2. 합성 데이터 생성 구현
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
interface SyntheticExample {
input: string;
output: string;
label?: string;
quality_score?: number;
}
// 분류 데이터 합성 생성
async function generateClassificationData(params: {
taskDescription: string;
labels: string[];
examplesPerLabel: number;
domainContext: string;
}): Promise<SyntheticExample[]> {
const examples: SyntheticExample[] = [];
for (const label of params.labels) {
const batchSize = 10;
const batches = Math.ceil(params.examplesPerLabel / batchSize);
for (let batch = 0; batch < batches; batch++) {
const count = Math.min(batchSize, params.examplesPerLabel - batch * batchSize);
const response = await client.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 4096,
messages: [{
role: 'user',
content: `다음 작업을 위한 합성 학습 데이터를 생성하세요.
작업: ${params.taskDescription}
도메인: ${params.domainContext}
레이블: ${label}
${count}개의 예시를 JSON 배열로 생성하세요. 각 예시는 다양한 표현, 길이, 어조를 사용하세요.
형식:
[{"input": "텍스트", "output": "분류 설명"}]
다양성을 위해:
- 짧은 문장부터 긴 문단까지 다양한 길이
- 구어체와 문어체 혼합
- 다양한 도메인 맥락`,
}],
temperature: 0.9,
});
try {
const text = response.content[0].type === 'text' ? response.content[0].text : '[]';
const jsonMatch = text.match(/\[[\s\S]*\]/);
if (jsonMatch) {
const batch_examples: any[] = JSON.parse(jsonMatch[0]);
examples.push(...batch_examples.map(e => ({ ...e, label })));
}
} catch { /* JSON 파싱 실패 시 스킵 */ }
}
}
return filterAndDeduplicate(examples);
}
// 품질 필터링
async function filterAndDeduplicate(examples: SyntheticExample[]): Promise<SyntheticExample[]> {
// 중복 제거 (간단 버전: 텍스트 정확 일치)
const seen = new Set<string>();
const unique = examples.filter(e => {
const key = e.input.trim().toLowerCase();
if (seen.has(key)) return false;
seen.add(key);
return true;
});
// LLM 품질 채점 (배치)
const scored = await Promise.all(
unique.map(async (example) => {
const score = await scoreQuality(example);
return { ...example, quality_score: score };
})
);
// 0.7 이상만 통과
return scored.filter(e => (e.quality_score ?? 0) >= 0.7);
}
async function scoreQuality(example: SyntheticExample): Promise<number> {
const response = await client.messages.create({
model: 'claude-haiku-4-5-20251001',
max_tokens: 10,
messages: [{
role: 'user',
content: `다음 예시의 품질을 0.0~1.0으로 평가하세요. 숫자만 출력하세요.
자연스러운가, 레이블과 일치하는가, 도메인에 적합한가 기준.
입력: ${example.input}
레이블: ${example.label}`,
}],
temperature: 0,
});
const text = response.content[0].type === 'text' ? response.content[0].text.trim() : '0.5';
return parseFloat(text) || 0.5;
}
// 파인튜닝 데이터셋 내보내기 (JSONL 형식)
function exportToJSONL(examples: SyntheticExample[]): string {
return examples.map(e => JSON.stringify({
messages: [
{ role: 'user', content: e.input },
{ role: 'assistant', content: e.output },
],
})).join('\n');
}
마무리
합성 데이터 품질의 핵심은 다양성과 정확성이다. temperature: 0.9로 다양한 표현을 생성하고, LLM 자기 평가로 저품질 데이터를 필터링한다. 실제 데이터 10~20%를 시드로 사용하면 도메인 특성이 유지된 합성 데이터를 만들 수 있다. 합성 데이터만으로 훈련할 때는 반드시 실제 데이터로 검증해야 한다.