LLM으로 합성 데이터 생성: 학습 데이터 부족 문제 해결

AI 기술

합성 데이터LLM데이터 증강파인튜닝머신러닝

이 글은 누구를 위한 것인가

  • 학습 데이터가 부족한 특수 도메인 모델을 훈련하려는 팀
  • 실제 데이터의 개인정보 문제를 합성 데이터로 해결하려는 개발자
  • 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%를 시드로 사용하면 도메인 특성이 유지된 합성 데이터를 만들 수 있다. 합성 데이터만으로 훈련할 때는 반드시 실제 데이터로 검증해야 한다.