Как вы делаете data quality для синтетических датасетов?

Краткий тезис

Качество синтетических датасетов критически важно для обучения и оценки RAG-систем и AI-агентов. Основные методы контроля качества включают LLM-as-judge для автоматической оценки примеров, self-consistency для проверки стабильности генерации, проверку на противоречия внутри датасета, human validation выборочной выборки и holdout для исключения пересечения с продакшен-данными. Комбинация этих подходов позволяет отсеять шум, повысить достоверность и избежать катастрофического забывания.


1. Термин: Синтетический датасет

Синтетический датасет — это набор данных, сгенерированный искусственно (обычно с помощью LLM), а не собранный из реальных пользовательских запросов. Он используется, когда реальных данных мало, они дороги в разметке или содержат чувствительную информацию.

Зачем нужен контроль качества

  • LLM может генерировать галлюцинации (фактически неверные ответы).
  • Модель может повторять одни и те же паттерны, снижая разнообразие (diversity).
  • Синтетические данные могут быть несбалансированными (например, слишком много лёгких вопросов).
  • Если синтетика похожа на продакшен-данные, оценка на ней будет смещённой (overly optimistic).

Основные риски

  • Шум (noise): нерелевантные или некорректные пары вопрос-ответ.
  • Противоречия (contradictions): один и тот же факт описан по-разному.
  • Утечка (leakage): синтетические примеры совпадают с тестовыми данными.

2. LLM-as-judge (Оценка с помощью LLM)

LLM-as-judge — это подход, при котором отдельная LLM (обычно более мощная, например GPT-4 или Claude) оценивает качество каждого сгенерированного примера по заданным критериям.

Процесс

  1. Для каждой пары (вопрос, ответ, контекст) отправляем запрос к judge-модели.
  2. Judge ставит оценку по шкале, например от 1 до 10, по критериям:
    • Полезность (helpfulness): отвечает ли ответ на вопрос?
    • Фактологическая точность (factual accuracy): соответствует ли ответ контексту?
    • Читаемость (readability): понятен ли язык?
  3. Отбрасываем примеры с оценкой ниже порога (например, < 7).

Пример промпта для judge

Оцените качество следующего примера для RAG-датасета по шкале 1-10.
Критерии: ответ должен быть полезным, точным и соответствовать контексту.
Вопрос: {question}
Контекст: {context}
Ответ: {answer}
Оценка (только число):

Преимущества

  • Автоматизация, масштабируемость.
  • Можно адаптировать под домен.

Недостатки

  • Judge-модель может быть предвзятой (bias).
  • Дорого (call|вызовы API).

Рекомендации

  • Использовать несколько judge-моделей и усреднять оценку.
  • Калибровать порог на небольшой выборке, проверенной человеком.

3. Self-consistency (Самосогласованность)

Self-consistency — метод, при котором на один и тот же вопрос генерируется несколько ответов (например, 3–5) с разной температурой или разными seed. Затем оценивается согласованность (agreement) между ответами.

Метрики согласованности

  • ROUGE-L или BLEU между парами ответов.
  • BERTScore (семантическая близость).
  • Процент совпадения ключевых фактов (если ответы структурированы).

Правило отбора

  • Если согласованность низкая (например, средний BERTScore < 0.85), пример считается ненадёжным и отбрасывается.
  • Если ответы сильно различаются, это может указывать на неоднозначность вопроса или нестабильность генерации.

Пример кода

from sentence_transformers import SentenceTransformer, util

model = SentenceTransformer('all-MiniLM-L6-v2')

def self_consistency_score(answers):
    embeddings = model.encode(answers)
    similarities = []
    for i in range(len(answers)):
        for j in range(i+1, len(answers)):
            sim = util.cos_sim(embeddings[i], embeddings[j]).item()
            similarities.append(sim)
    return sum(similarities) / len(similarities)

answers = ["Ответ A", "Ответ B", "Ответ C"]
score = self_consistency_score(answers)
if score < 0.8:
    print("Пример нестабилен, отбрасываем")

4. Проверка на противоречия (Contradiction Detection)

Внутри датасета не должно быть примеров, где один и тот же факт описан по-разному. Например, вопрос "Какова столица Франции?" в одном примере имеет ответ "Париж", а в другом — "Лион".

Методы

  • Семантическая кластеризация: группируем вопросы по эмбеддингам, внутри кластера проверяем ответы на противоречия.
  • LLM-based проверка: для каждой пары (вопрос1, ответ1) и (вопрос2, ответ2) с высокой семантической близостью вопросов judge-модель определяет, противоречат ли ответы друг другу.
  • Правила на основе NER: если в ответах разные сущности на одно и то же место, это подозрительно.

Пример:

from sklearn.cluster import DBSCAN
from sentence_transformers import SentenceTransformer

model = SentenceTransformer('all-MiniLM-L6-v2')
questions = ["Столица Франции?", "Какой город является столицей Франции?"]
embeddings = model.encode(questions)
clustering = DBSCAN(eps=0.5, min_samples=2).fit(embeddings)
# Если вопросы в одном кластере, проверяем ответы на противоречия

Порог если доля противоречивых пар в кластере > 5%, кластер перегенерируется или удаляется.


5. Human validation (Валидация человеком)

Полная ручная проверка всех синтетических примеров невозможна из-за стоимости. Поэтому используется выборочная проверка (sampling).

Процесс

  1. Случайно отбираем 5–10% датасета.
  2. Аннотаторы (или команда) оценивают каждый пример по шкале:
    • Accept (принять) — пример корректен.
    • Reject (отклонить) — пример содержит ошибку.
    • Fix (исправить) — пример можно доработать.
  3. Считаем метрики:
  4. Если acceptance rate < 80%, пересматриваем процесс генерации или увеличиваем выборку.

Совет Использовать платформы вроде Label Studio или Prodigy для разметки.


6. Holdout и семантическое пересечение (Semantic Similarity Check)

Holdout — это отложенный набор реальных данных (production), который не используется при генерации синтетики. Необходимо убедиться, что синтетические примеры не пересекаются с holdout, иначе оценка на holdout будет завышена.

Метод

  1. Берём все синтетические вопросы и все вопросы из holdout.
  2. Вычисляем семантическую близость (cosine similarity эмбеддингов) между каждой парой.
  3. Если для какого-то синтетического вопроса находится holdout-вопрос с similarity > 0.9, такой синтетический пример удаляем (или переформулируем).

Инструменты

  • Sentence Transformers для эмбеддингов.
  • FAISS или Annoy для быстрого поиска ближайших соседей.

Пример:

import faiss
from sentence_transformers import SentenceTransformer

model = SentenceTransformer('all-MiniLM-L6-v2')
synth_questions = [...]  # список синтетических вопросов
holdout_questions = [...]  # список продакшен-вопросов

synth_emb = model.encode(synth_questions)
holdout_emb = model.encode(holdout_questions)

index = faiss.IndexFlatIP(holdout_emb.shape[1])
index.add(holdout_emb)
D, I = index.search(synth_emb, k=1)  # ближайший сосед
max_sim = D.max()
if max_sim > 0.9:
    print("Есть пересечение, нужно удалить или переформулировать")

7. Дополнительные методы контроля качества

7.1 Проверка формата (Format Validation)

  • Убедиться, что ответы соответствуют ожидаемой структуре (JSON, Markdown, таблица).
  • Использовать pydantic или JSON Schema для валидации.

7.2 Фактологическая точность (Factual Accuracy)

  • Если контекст известен (например, из базы знаний), можно проверить, что ответ не противоречит контексту.
  • Использовать NLI-модели (Natural Language Inference) для проверки entailment/contradiction.

7.3 Разнообразие (Diversity)

  • Измерить внутрикластерное расстояние между вопросами. Если все вопросы слишком похожи, датасет нерепрезентативен.
  • Использовать intra-list similarity (средняя попарная косинусная близость). Порог: < 0.6.

7.4 Баланс классов (Class Balance)

  • Для задач классификации или генерации с метками проверить распределение меток. Если какой-то класс составляет < 5%, добавить примеров.

8. Инструменты и пайплайн (пример на Python)

Ниже — пример пайплайна контроля качества синтетического датасета.

import pandas as pd
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np

class SyntheticDataQuality:
    def __init__(self, judge_model_name='gpt-4', threshold_judge=7, threshold_consistency=0.8):
        self.judge_model = judge_model_name  # для LLM-as-judge
        self.threshold_judge = threshold_judge
        self.threshold_consistency = threshold_consistency
        self.embedder = SentenceTransformer('all-MiniLM-L6-v2')
    
    def llm_judge(self, question, context, answer):
        # вызов LLM (упрощённо)
        score = 8  # заглушка
        return score
    
    def self_consistency(self, question, n=3):
        # генерация n ответов (заглушка)
        answers = [f"answer_{i}" for i in range(n)]
        emb = self.embedder.encode(answers)
        sim_matrix = cosine_similarity(emb)
        avg_sim = (sim_matrix.sum() - n) / (n * (n-1))
        return avg_sim
    
    def check_contradictions(self, df, eps=0.5):
        # кластеризация вопросов
        emb = self.embedder.encode(df['question'].tolist())
        from sklearn.cluster import DBSCAN
        clusters = DBSCAN(eps=eps, min_samples=2).fit_predict(emb)
        df['cluster'] = clusters
        contradictions = []
        for cluster_id in set(clusters):
            if cluster_id == -1:
                continue
            cluster_df = df[df['cluster'] == cluster_id]
            if len(cluster_df) < 2:
                continue
            # здесь можно вызвать LLM для проверки противоречий
        return contradictions
    
    def run_pipeline(self, df_synthetic, holdout_questions=None):
        # 1. LLM-as-judge
        scores = []
        for _, row in df_synthetic.iterrows():
            score = self.llm_judge(row['question'], row['context'], row['answer'])
            scores.append(score)
        df_synthetic['judge_score'] = scores
        df_synthetic = df_synthetic[df_synthetic['judge_score'] >= self.threshold_judge]
        
        # 2. Self-consistency
        consistency_scores = []
        for q in df_synthetic['question']:
            cs = self.self_consistency(q)
            consistency_scores.append(cs)
        df_synthetic['consistency'] = consistency_scores
        df_synthetic = df_synthetic[df_synthetic['consistency'] >= self.threshold_consistency]
        
        # 3. Проверка на противоречия (упрощённо)
        contradictions = self.check_contradictions(df_synthetic)
        # удаляем проблемные строки
        
        # 4. Holdout check
        if holdout_questions is not None:
            holdout_emb = self.embedder.encode(holdout_questions)
            synth_emb = self.embedder.encode(df_synthetic['question'].tolist())
            sim = cosine_similarity(synth_emb, holdout_emb)
            max_sim = sim.max(axis=1)
            df_synthetic = df_synthetic[max_sim < 0.9]
        
        # 5. Human validation (выборка)
        sample = df_synthetic.sample(frac=0.05)
        # отправить на ручную проверку
        
        return df_synthetic

9. Метрики качества датасета

После фильтрации полезно посчитать итоговые метрики:

МетрикаОписаниеЦелевое значение
Acceptance rateДоля примеров, прошедших все автоматические проверки> 80%
Human acceptance rateДоля принятых человеком в выборке> 90%
Average judge scoreСредняя оценка LLM-as-judge> 7.5
Consistency scoreСредняя самосогласованность> 0.85
Contradiction rateДоля противоречивых пар в кластерах< 2%
Max similarity to holdoutМаксимальная семантическая близость к продакшену< 0.85

Пет-проект для закрепления

Задача Разработать пайплайн контроля качества для синтетического датасета вопросов-ответов по документации библиотеки LangChain.

Инструменты

Шаги:

  1. Сгенерировать 500 синтетических примеров с помощью GPT-4 (вопрос, контекст, ответ).
  2. Реализовать модуль LLM-as-judge (использовать GPT-4-mini для оценки).
  3. Реализовать self-consistency (3 генерации на вопрос, BERTScore).
  4. Реализовать проверку на противоречия (кластеризация + LLM).
  5. Взять 50 реальных вопросов из чата поддержки LangChain как holdout, проверить пересечение.
  6. Отобрать 10% для ручной проверки (можно самому).
  7. Посчитать итоговые метрики и построить дашборд в Streamlit.

Ожидаемый результат Пайплайн, который принимает сырой CSV с синтетическими данными и возвращает отфильтрованный датасет с отчётом о качестве (доля отбракованных примеров, средние оценки, список проблемных кластеров).


Связь с другими вопросами

ВопросТема
533Как генерировать синтетические данные для обучения RAG?
535Как оценивать качество работы AI-агента?
536Как тестировать agentic RAG-систему?
537Как деплоить agentic RAG-систему?
538Как мониторить agentic RAG-систему?
5Как оценивать качество retrieval'а в RAG?

Навигация