본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout

내비게이션

  • Tech
  • Projects
  • Books
  • About
  • Tags

카테고리

  • AI / ML
  • 웹 개발
  • 프로그래밍
  • 개발 도구

연결

  • GitHub
  • Email
  • RSS
© 2026 Kreath Archive. All rights reserved.Built with Next.js + MDX
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 5장: 데이터 품질 검증과 필터링 파이프라인
2026년 3월 29일·AI / ML·

5장: 데이터 품질 검증과 필터링 파이프라인

충실도, 유용성, 프라이버시 3계층 품질 평가 프레임워크와 LLM-as-Judge, 자동 필터링 파이프라인, 중복 제거 전략을 다룹니다.

18분1,159자7개 섹션
synthetic-dataaidata-engineeringllmmlops
공유
synthetic-data5 / 10
12345678910
이전4장: 구조화된 데이터와 멀티모달 합성다음6장: 데이터 증강 기법

이 장에서 배우는 것

  • 3계층 품질 평가 프레임워크: 충실도, 유용성, 프라이버시
  • 자동 품질 검증 파이프라인 구축
  • LLM-as-Judge 기법과 한계
  • 인간 평가와 자동 평가의 통합 전략
  • 중복 및 노이즈 제거 기법
  • 실전 필터링 파이프라인 구현

3계층 품질 평가 프레임워크

합성 데이터의 품질은 단일 지표로 측정할 수 없습니다. 충실도(Fidelity), 유용성(Utility), 프라이버시(Privacy) 세 축으로 구성된 평가 프레임워크가 업계 표준으로 자리잡고 있습니다.

계층 1: 충실도(Fidelity)

충실도는 합성 데이터가 원본 데이터의 통계적 특성을 얼마나 잘 보존하는지를 측정합니다.

분포 유사도: 각 특성(feature)의 분포가 원본과 얼마나 유사한지를 KL 발산(Kullback-Leibler Divergence), JS 발산(Jensen-Shannon Divergence), KS 검정(Kolmogorov-Smirnov Test) 등으로 측정합니다.

fidelity_metrics.py
python
import numpy as np
from scipy import stats
from scipy.spatial.distance import jensenshannon
 
 
def compute_fidelity_metrics(
    real: np.ndarray, synthetic: np.ndarray
) -> dict:
    """충실도 메트릭을 계산합니다."""
    metrics = {}
 
    # KS 검정: 두 분포의 최대 차이
    ks_stat, ks_pvalue = stats.ks_2samp(real, synthetic)
    metrics["ks_statistic"] = ks_stat
    metrics["ks_pvalue"] = ks_pvalue
 
    # JS 발산: 두 분포의 대칭적 거리 (0~1)
    # 히스토그램으로 이산화
    bins = np.linspace(
        min(real.min(), synthetic.min()),
        max(real.max(), synthetic.max()),
        50,
    )
    real_hist, _ = np.histogram(real, bins=bins, density=True)
    synth_hist, _ = np.histogram(synthetic, bins=bins, density=True)
 
    # 0 방지를 위한 스무딩
    real_hist = real_hist + 1e-10
    synth_hist = synth_hist + 1e-10
 
    js_div = jensenshannon(real_hist, synth_hist)
    metrics["js_divergence"] = js_div
 
    # 기본 통계량 비교
    metrics["mean_diff"] = abs(real.mean() - synthetic.mean())
    metrics["std_diff"] = abs(real.std() - synthetic.std())
    metrics["median_diff"] = abs(
        np.median(real) - np.median(synthetic)
    )
 
    return metrics

상관관계 보존: 다변량 데이터에서 변수 간 상관관계가 유지되는지 확인합니다. 상관관계 행렬의 프로베니우스 노름(Frobenius Norm) 차이를 주로 사용합니다.

계층 2: 유용성(Utility)

유용성은 합성 데이터가 실제 다운스트림 태스크에서 얼마나 효과적인지를 측정합니다. 가장 대표적인 방법이 TSTR(Train on Synthetic, Test on Real)입니다.

utility_metrics.py
python
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.linear_model import LogisticRegression
import numpy as np
 
 
def tstr_evaluation(
    synthetic_X: np.ndarray,
    synthetic_y: np.ndarray,
    real_X: np.ndarray,
    real_y: np.ndarray,
) -> dict:
    """TSTR 평가를 수행합니다."""
    classifiers = {
        "logistic_regression": LogisticRegression(max_iter=1000),
        "random_forest": RandomForestClassifier(n_estimators=100),
        "gradient_boosting": GradientBoostingClassifier(
            n_estimators=100
        ),
    }
 
    results = {}
 
    for name, clf in classifiers.items():
        # 합성 데이터로 학습
        clf.fit(synthetic_X, synthetic_y)
 
        # 실제 데이터로 평가
        score = clf.score(real_X, real_y)
        results[f"tstr_{name}"] = score
 
    # 비교: 실제 데이터로 학습했을 때의 성능 (TRTR)
    for name, clf in classifiers.items():
        scores = cross_val_score(clf, real_X, real_y, cv=5)
        results[f"trtr_{name}"] = scores.mean()
 
    # TSTR / TRTR 비율 계산
    for name in classifiers:
        tstr = results[f"tstr_{name}"]
        trtr = results[f"trtr_{name}"]
        results[f"ratio_{name}"] = tstr / trtr if trtr > 0 else 0
 
    return results
Info

TSTR/TRTR 비율이 0.95 이상이면 합성 데이터가 실제 데이터를 거의 완벽하게 대체할 수 있음을 의미합니다. 0.85~0.95는 "사용 가능" 수준, 0.85 미만은 품질 개선이 필요한 수준으로 판단합니다.

계층 3: 프라이버시(Privacy)

프라이버시 메트릭은 합성 데이터에서 원본 개인정보가 유출되는지를 측정합니다. 7장에서 더 자세히 다루지만, 품질 파이프라인의 일부로 기본적인 프라이버시 검사를 포함해야 합니다.

privacy_metrics.py
python
from sklearn.neighbors import NearestNeighbors
import numpy as np
 
 
def distance_to_closest_record(
    synthetic: np.ndarray, real: np.ndarray
) -> dict:
    """합성 데이터와 실제 데이터 간 최근접 거리를 계산합니다."""
    nn = NearestNeighbors(n_neighbors=1, metric="euclidean")
    nn.fit(real)
 
    distances, _ = nn.kneighbors(synthetic)
    distances = distances.flatten()
 
    return {
        "min_distance": distances.min(),
        "mean_distance": distances.mean(),
        "median_distance": np.median(distances),
        "pct_below_threshold": (distances < 0.01).mean(),
    }

LLM-as-Judge

텍스트 합성 데이터의 품질 평가에서 가장 혁신적인 접근법이 LLM-as-Judge입니다. LLM을 평가자로 활용하여 생성된 데이터의 품질을 자동으로 판정합니다.

평가 프롬프트 설계

llm_judge.py
python
JUDGE_PROMPT = """당신은 AI 학습 데이터의 품질을 평가하는 전문가입니다.
다음 지시-응답 쌍의 품질을 5개 차원에서 1~5점으로 평가하세요.
 
지시문: {instruction}
응답문: {response}
 
평가 차원:
1. 정확성(Accuracy): 정보가 사실에 부합하는가
2. 완전성(Completeness): 지시에 충분히 답변했는가
3. 명확성(Clarity): 설명이 이해하기 쉬운가
4. 관련성(Relevance): 지시와 응답이 일관되는가
5. 유용성(Helpfulness): 실제 사용자에게 도움이 되는가
 
각 차원에 대해 점수와 간단한 근거를 제공하세요.
 
JSON 형식으로 출력:
{{
  "accuracy": {{"score": N, "reason": "..."}},
  "completeness": {{"score": N, "reason": "..."}},
  "clarity": {{"score": N, "reason": "..."}},
  "relevance": {{"score": N, "reason": "..."}},
  "helpfulness": {{"score": N, "reason": "..."}},
  "overall": N,
  "recommendation": "accept/revise/reject"
}}"""
 
 
import json
from dataclasses import dataclass
 
 
@dataclass
class QualityScore:
    accuracy: float
    completeness: float
    clarity: float
    relevance: float
    helpfulness: float
    overall: float
    recommendation: str
 
 
async def evaluate_with_llm_judge(
    instruction: str,
    response: str,
    model_fn,
) -> QualityScore:
    """LLM-as-Judge로 데이터 품질을 평가합니다."""
    prompt = JUDGE_PROMPT.format(
        instruction=instruction,
        response=response,
    )
 
    result = await model_fn(prompt)
    parsed = json.loads(result)
 
    return QualityScore(
        accuracy=parsed["accuracy"]["score"],
        completeness=parsed["completeness"]["score"],
        clarity=parsed["clarity"]["score"],
        relevance=parsed["relevance"]["score"],
        helpfulness=parsed["helpfulness"]["score"],
        overall=parsed["overall"],
        recommendation=parsed["recommendation"],
    )

LLM-as-Judge의 한계와 보완

LLM 판정에는 알려진 편향이 존재합니다.

  1. 위치 편향(Position Bias): 비교 평가 시 첫 번째 또는 마지막에 위치한 응답에 높은 점수를 주는 경향이 있습니다.
  2. 장문 편향(Length Bias): 더 긴 응답에 높은 점수를 부여하는 경향이 있습니다.
  3. 자기 선호 편향(Self-Preference): 같은 모델 계열이 생성한 응답에 높은 점수를 주는 경향이 있습니다.
Warning

LLM-as-Judge의 편향을 완화하려면 다음 전략을 사용합니다: (1) 여러 LLM으로 교차 평가, (2) 비교 평가 시 순서를 랜덤화, (3) 점수 기준을 구체적 루브릭으로 명시, (4) 인간 평가와의 일치도를 주기적으로 검증합니다.


인간 평가 통합

자동 평가만으로는 한계가 있습니다. 인간 평가를 효율적으로 통합하는 전략이 필요합니다.

계층적 평가 전략

전체 데이터에 인간 평가를 적용하는 것은 비현실적입니다. 대신 다음과 같은 계층적 접근을 사용합니다.

  1. 1차 자동 필터: 형식 검증, 길이 제한, 언어 감지 등 기본적인 규칙 기반 필터
  2. 2차 LLM-as-Judge: 의미적 품질, 정확성, 관련성 등 고수준 평가
  3. 3차 인간 평가: 2차를 통과한 데이터의 5~10%를 랜덤 샘플링하여 인간이 검증
  4. 피드백 루프: 인간 평가 결과를 자동 평가 기준에 반영하여 지속적으로 보정

중복 및 노이즈 제거

정확 중복 제거

해시 기반으로 동일한 데이터를 제거합니다.

deduplication.py
python
import hashlib
from collections import defaultdict
 
 
def exact_dedup(data: list[dict], key: str = "text") -> list[dict]:
    """정확한 중복을 해시 기반으로 제거합니다."""
    seen_hashes: set[str] = set()
    unique_data: list[dict] = []
 
    for item in data:
        text_hash = hashlib.md5(
            item[key].encode("utf-8")
        ).hexdigest()
        if text_hash not in seen_hashes:
            seen_hashes.add(text_hash)
            unique_data.append(item)
 
    return unique_data

유사 중복 제거(Near-Deduplication)

MinHash와 LSH(Locality-Sensitive Hashing)를 활용하여 의미적으로 유사한 데이터를 탐지합니다.

near_deduplication.py
python
from datasketch import MinHash, MinHashLSH
 
 
def near_dedup_minhash(
    data: list[dict],
    key: str = "text",
    threshold: float = 0.8,
    num_perm: int = 128,
) -> list[dict]:
    """MinHash LSH로 유사 중복을 제거합니다."""
    lsh = MinHashLSH(threshold=threshold, num_perm=num_perm)
    minhashes: dict[int, MinHash] = {}
 
    # MinHash 생성 및 LSH에 삽입
    for idx, item in enumerate(data):
        mh = MinHash(num_perm=num_perm)
        # 3-gram 기반 shingling
        text = item[key]
        for i in range(len(text) - 2):
            shingle = text[i:i + 3]
            mh.update(shingle.encode("utf-8"))
        minhashes[idx] = mh
 
        try:
            lsh.insert(str(idx), mh)
        except ValueError:
            pass  # 이미 유사한 항목이 존재
 
    # 중복 그룹에서 대표만 유지
    keep_indices: set[int] = set()
    visited: set[int] = set()
 
    for idx in range(len(data)):
        if idx in visited:
            continue
        keep_indices.add(idx)
        # 유사 항목 탐색
        result = lsh.query(minhashes[idx])
        for r in result:
            visited.add(int(r))
 
    return [data[i] for i in sorted(keep_indices)]

임베딩 기반 다양성 필터

임베딩 공간에서 데이터 포인트 간 거리를 분석하여, 밀집 영역의 데이터를 제거하고 희소 영역의 데이터를 보존합니다.

diversity_filter.py
python
import numpy as np
from sklearn.cluster import KMeans
 
 
def diversity_sampling(
    embeddings: np.ndarray,
    data: list[dict],
    target_size: int,
    n_clusters: int = 50,
) -> list[dict]:
    """클러스터링 기반 다양성 샘플링을 수행합니다."""
    # K-Means 클러스터링
    kmeans = KMeans(n_clusters=n_clusters, random_state=42)
    labels = kmeans.fit_predict(embeddings)
 
    # 각 클러스터에서 비례 샘플링
    samples_per_cluster = max(1, target_size // n_clusters)
    selected_indices: list[int] = []
 
    for cluster_id in range(n_clusters):
        cluster_indices = np.where(labels == cluster_id)[0]
 
        if len(cluster_indices) <= samples_per_cluster:
            selected_indices.extend(cluster_indices.tolist())
        else:
            # 클러스터 중심에서 가장 먼 것부터 선택 (다양성 최대화)
            center = kmeans.cluster_centers_[cluster_id]
            distances = np.linalg.norm(
                embeddings[cluster_indices] - center, axis=1
            )
            sorted_idx = np.argsort(-distances)
            selected = cluster_indices[
                sorted_idx[:samples_per_cluster]
            ]
            selected_indices.extend(selected.tolist())
 
    return [data[i] for i in selected_indices[:target_size]]
Info

다양성 필터링은 직관에 반하는 것처럼 보일 수 있습니다. 데이터를 "줄이는" 것인데 왜 성능이 향상되는 것일까요? 핵심은 중복된 패턴을 학습하는 데 낭비되는 학습 용량을 다양한 패턴 학습에 재할당하는 효과가 있기 때문입니다.


실전 필터링 파이프라인

모든 요소를 통합한 실전 필터링 파이프라인을 구현합니다.

quality_pipeline.py
python
from dataclasses import dataclass
from enum import Enum
 
 
class FilterResult(Enum):
    ACCEPT = "accept"
    REVISE = "revise"
    REJECT = "reject"
 
 
@dataclass
class PipelineConfig:
    min_length: int = 50
    max_length: int = 5000
    min_quality_score: float = 3.5
    dedup_threshold: float = 0.8
    diversity_target: int = 10000
    enable_human_review: bool = True
    human_sample_rate: float = 0.05
 
 
class QualityPipeline:
    """합성 데이터 품질 검증 통합 파이프라인"""
 
    def __init__(self, config: PipelineConfig):
        self.config = config
        self.stats = {
            "total": 0,
            "format_filtered": 0,
            "quality_filtered": 0,
            "dedup_filtered": 0,
            "final": 0,
        }
 
    def run(self, data: list[dict]) -> list[dict]:
        """전체 파이프라인을 실행합니다."""
        self.stats["total"] = len(data)
 
        # 1단계: 형식 필터링
        data = self._format_filter(data)
        self.stats["format_filtered"] = (
            self.stats["total"] - len(data)
        )
 
        # 2단계: 품질 평가 및 필터링
        data = self._quality_filter(data)
        self.stats["quality_filtered"] = (
            self.stats["total"]
            - self.stats["format_filtered"]
            - len(data)
        )
 
        # 3단계: 중복 제거
        data = self._dedup_filter(data)
 
        # 4단계: 다양성 샘플링
        if len(data) > self.config.diversity_target:
            data = self._diversity_sample(data)
 
        self.stats["final"] = len(data)
        return data
 
    def _format_filter(self, data: list[dict]) -> list[dict]:
        """형식 기반 필터링"""
        filtered = []
        for item in data:
            text = item.get("response", item.get("text", ""))
            if self.config.min_length <= len(text) <= self.config.max_length:
                filtered.append(item)
        return filtered
 
    def _quality_filter(self, data: list[dict]) -> list[dict]:
        """LLM-as-Judge 기반 품질 필터링"""
        # 실제 구현에서는 LLM-as-Judge 호출
        return [
            item for item in data
            if item.get("quality_score", 5) >= self.config.min_quality_score
        ]
 
    def _dedup_filter(self, data: list[dict]) -> list[dict]:
        """중복 제거"""
        data = exact_dedup(data, key="response")
        data = near_dedup_minhash(
            data,
            key="response",
            threshold=self.config.dedup_threshold,
        )
        return data
 
    def _diversity_sample(self, data: list[dict]) -> list[dict]:
        """다양성 기반 샘플링"""
        # 임베딩 생성 후 다양성 샘플링
        return data[:self.config.diversity_target]
 
    def report(self) -> str:
        """파이프라인 실행 결과를 보고합니다."""
        return (
            f"총 입력: {self.stats['total']}\n"
            f"형식 필터: -{self.stats['format_filtered']}\n"
            f"품질 필터: -{self.stats['quality_filtered']}\n"
            f"중복 제거: -{self.stats['dedup_filtered']}\n"
            f"최종 출력: {self.stats['final']}\n"
            f"통과율: "
            f"{self.stats['final'] / self.stats['total'] * 100:.1f}%"
        )

정리

이 장에서는 합성 데이터 품질 검증의 전체 프레임워크를 구축했습니다.

  • 3계층 평가: 충실도(분포 유사도), 유용성(TSTR), 프라이버시(거리 기반)로 다각도 검증합니다.
  • LLM-as-Judge: 텍스트 데이터의 의미적 품질을 자동 평가하되, 위치/장문/자기선호 편향에 주의합니다.
  • 인간 평가 통합: 자동 평가를 1, 2차로, 인간 평가를 3차로 배치하는 계층적 전략이 효율적입니다.
  • 중복 제거: 정확 중복(해시), 유사 중복(MinHash LSH), 다양성 샘플링(클러스터링)을 순차 적용합니다.
  • 통합 파이프라인: 형식 필터, 품질 필터, 중복 제거, 다양성 샘플링을 하나의 파이프라인으로 연결합니다.
Tip

다음 장에서는 기존 데이터를 증강(Augmentation)하는 기법을 다룹니다. 합성 데이터 "생성"과 데이터 "증강"은 상호 보완적인 관계에 있으며, 둘을 결합하면 최상의 결과를 얻을 수 있습니다.

이 글이 도움이 되셨나요?

관련 주제 더 보기

#synthetic-data#ai#data-engineering#llm#mlops

관련 글

AI / ML

6장: 데이터 증강 기법

전통적 텍스트 증강부터 LLM 기반 증강, 어려운 예제 생성, 엣지 케이스 증강, 증강 비율 최적화까지 실전 데이터 증강 기법을 다룹니다.

2026년 3월 31일·21분
AI / ML

4장: 구조화된 데이터와 멀티모달 합성

테이블/CSV 합성, JSON/SQL 데이터 생성, 이미지-텍스트 페어 생성, NVIDIA Nemotron 등 멀티모달 합성 데이터 생성 기법을 다룹니다.

2026년 3월 27일·19분
AI / ML

7장: 프라이버시 보존 합성 데이터

차등 프라이버시, PII 마스킹, 멤버십 추론 공격 방어, 유사도 필터, 규제 대응 전략과 프라이버시-유용성 트레이드오프를 다룹니다.

2026년 4월 2일·20분
이전 글4장: 구조화된 데이터와 멀티모달 합성
다음 글6장: 데이터 증강 기법

댓글

목차

약 18분 남음
  • 이 장에서 배우는 것
  • 3계층 품질 평가 프레임워크
    • 계층 1: 충실도(Fidelity)
    • 계층 2: 유용성(Utility)
    • 계층 3: 프라이버시(Privacy)
  • LLM-as-Judge
    • 평가 프롬프트 설계
    • LLM-as-Judge의 한계와 보완
  • 인간 평가 통합
    • 계층적 평가 전략
  • 중복 및 노이즈 제거
    • 정확 중복 제거
    • 유사 중복 제거(Near-Deduplication)
    • 임베딩 기반 다양성 필터
  • 실전 필터링 파이프라인
  • 정리