Welcome to my personal place for love, peace and happiness 🤖

Генерация синтетических данных с помощью SDV: от идеи до готовой таблицы в ClickHouse

Синтетические данные давно перестали быть просто «искусственной картинкой» — сегодня это полноценный инструмент для тестирования, разработки и даже обучения моделей машинного обучения. В этой статье я на примере реального кода покажу, как с помощью библиотеки SDV ( Synthetic Data Vault ) можно сгенерировать синтетическую копию существующей таблицы из ClickHouse, сохранив её статистические свойства и взаимосвязи.

Весь код, который мы разберём, написан на Python и использует актуальную версию SDV. Спойлер: итоговый скрипт занимает меньше 80 строк, а результат — полностью готовая синтетическая таблица в вашей базе данных.


Что такое SDV и зачем он нужен

Synthetic Data Vault (SDV) — это Python-библиотека с открытым исходным кодом, которая стала фактическим стандартом для генерации синтетических табличных данных. Она была создана в MIT, а сегодня развивается компанией DataCebo.

Главная идея SDV проста: вы даёте ей реальные данные, она обучает генеративную модель, а затем эта модель создаёт новые данные, которые статистически неотличимы от оригинала, но при этом не содержат реальных записей.

Ключевые возможности SDV:

  • Обучение на одном наборе данных — библиотека анализирует распределения, корреляции и зависимости между колонками.
  • Генерация новых записей — синтетические данные повторяют структуру и свойства оригинала.
  • Оценка качества — встроенные метрики показывают, насколько хорошо синтетика отражает реальные данные.
  • Поддержка разных типов данных — числовые, категориальные, даты, текстовые поля.

SDV умеет работать с одиночными таблицами, связанными таблицами и даже последовательными данными.


Какие синтезаторы предлагает SDV

SDV предлагает несколько алгоритмов (синтезаторов) на выбор. В нашем примере мы используем `GaussianCopulaSynthesizer` — и вот почему.

Синтезатор Принцип работы Скорость Когда использовать
GaussianCopulaSynthesizer Классический статистический метод, моделирует данные через многомерное распределение Очень быстрый Рекомендуется для старта: быстрая работа, хорошее качество, прозрачность
CTGANSynthesizer Генеративно-состязательная сеть (GAN) Медленнее Для данных со сложными зависимостями или большим количеством категориальных признаков
TVAESynthesizer Вариационный автокодировщик Средняя Альтернатива GAN, требующая больше данных для обучения

`GaussianCopulaSynthesizer` отлично справляется со смешанными типами данных, которые встречаются в реальных таблицах: числа, строки, даты. При этом он обучается за секунды даже на десятках тысяч строк. Именно поэтому мы выбрали его для нашего примера.


Настройка окружения

Перед запуском кода нам потребуется установить несколько пакетов. В нашем проекте мы использовали `uv` — быстрый менеджер пакетов для Python.

uv pip install torch==2.2.2
uv pip install "numpy<2"
uv pip install "scipy<1.13"
uv pip install sdv
uv pip install clickhouse-connect

Важно: версии пакетов подобраны так, чтобы избежать конфликтов. На момент написания статьи `torch` версии 2.13.0 не имеет готовых сборок для macOS на Intel, поэтому мы используем `2.2.2`. Также важно зафиксировать `numpy<2` и `scipy<1.13` — более новые версии несовместимы с текущей версией `torch`.


Полный код генерации синтетических данных

Ниже представлен итоговый скрипт `sdv_py_v5.py`, который мы будем разбирать. Он подключается к ClickHouse, загружает реальные данные, обучает синтезатор, генерирует синтетическую копию и сохраняет результат обратно в базу.

import clickhouse_connect
import pandas as pd
import os
import warnings
from sdv.single_table import GaussianCopulaSynthesizer
from sdv.metadata import SingleTableMetadata

# Подавляем предупреждения (включая депрекацию)
warnings.filterwarnings("ignore")

# Правильный импорт для однотабличных отчётов
from sdmetrics.reports.single_table import QualityReport, DiagnosticReport

# ========== НАСТРОЙКИ ==========
CLICKHOUSE_HOST = 'localhost'
CLICKHOUSE_PORT = 18123
CLICKHOUSE_USER = 'default'
CLICKHOUSE_PASSWORD = 'changeme'
CLICKHOUSE_DATABASE = 'default'

SOURCE_TABLE = 'superstore'
SYNTHETIC_TABLE = f'{SOURCE_TABLE}_synt'
LIMIT_ROWS = 10000
NUM_SYNTHETIC_ROWS = None

# ========== ПОДКЛЮЧЕНИЕ ==========
client = clickhouse_connect.get_client(
    host=CLICKHOUSE_HOST,
    port=CLICKHOUSE_PORT,
    username=CLICKHOUSE_USER,
    password=CLICKHOUSE_PASSWORD,
    database=CLICKHOUSE_DATABASE
)

# ========== ЗАГРУЗКА ==========
query = f"SELECT * FROM {SOURCE_TABLE} LIMIT {LIMIT_ROWS}"
real_df = client.query_df(query)
print(f"Загружено {len(real_df)} строк")

# Преобразуем числовые колонки (запятая → точка)
numeric_cols = ['sales', 'discount', 'profit']
for col in numeric_cols:
    if col in real_df.columns and real_df[col].dtype == 'object':
        real_df[col] = real_df[col].str.replace(',', '.').astype(float)

# ========== МЕТАДАННЫЕ ==========
metadata = SingleTableMetadata()
metadata.detect_from_dataframe(real_df)

if os.path.exists('metadata.json'):
    os.remove('metadata.json')
metadata.save_to_json('metadata.json')
print("Метаданные сохранены в metadata.json")

# ========== ОБУЧЕНИЕ ==========
print("Начинаем обучение синтезатора...")
synthesizer = GaussianCopulaSynthesizer(metadata)
synthesizer.fit(real_df)
print("Обучение завершено")

# ========== ГЕНЕРАЦИЯ ==========
if NUM_SYNTHETIC_ROWS is None:
    NUM_SYNTHETIC_ROWS = len(real_df)
synthetic_df = synthesizer.sample(NUM_SYNTHETIC_ROWS)
print(f"Сгенерировано {len(synthetic_df)} строк")

# ========== ПРЕДПРОСМОТР ==========
print("\nОригинальные данные (первые 5 строк):")
print(real_df.head())
print("\nСинтетические данные (первые 5 строк):")
print(synthetic_df.head())

# ========== ОЦЕНКА КАЧЕСТВА ==========
print("\n--- Диагностика (проверка ошибок) ---")
diagnostic = DiagnosticReport()
diagnostic.generate(real_df, synthetic_df, metadata.to_dict())
print(diagnostic)

print("\n--- Оценка качества (статистическое сходство) ---")
quality = QualityReport()
quality.generate(real_df, synthetic_df, metadata.to_dict())
print(quality)

# ========== СОХРАНЕНИЕ В CLICKHOUSE ==========
client.command(f"CREATE TABLE IF NOT EXISTS {SYNTHETIC_TABLE} AS {SOURCE_TABLE}")
client.insert_df(SYNTHETIC_TABLE, synthetic_df)
print(f"\nСинтетические данные загружены в таблицу {SYNTHETIC_TABLE}")

# ========== СОХРАНЕНИЕ В CSV ==========
synthetic_df.to_csv('synthetic_table.csv', index=False)
print("Синтетические данные также сохранены в synthetic_table.csv")

Разбор кода по шагам

1. Подключение к ClickHouse и загрузка данных

client = clickhouse_connect.get_client(
    host=CLICKHOUSE_HOST,
    port=CLICKHOUSE_PORT,
    username=CLICKHOUSE_USER,
    password=CLICKHOUSE_PASSWORD,
    database=CLICKHOUSE_DATABASE
)

query = f"SELECT * FROM {SOURCE_TABLE} LIMIT {LIMIT_ROWS}"
real_df = client.query_df(query)

Мы подключаемся к ClickHouse через библиотеку `clickhouse-connect` и загружаем данные из таблицы-источника в Pandas DataFrame. Лимит в 10 000 строк — разумный компромисс между качеством обучения и скоростью. Для большинства задач этого объёма достаточно, чтобы синтезатор уловил основные закономерности.

2. Предобработка числовых данных

numeric_cols = ['sales', 'discount', 'profit']
for col in numeric_cols:
    if col in real_df.columns and real_df[col].dtype == 'object':
        real_df[col] = real_df[col].str.replace(',', '.').astype(float)

В нашем примере числовые колонки хранятся в ClickHouse как строки с запятой в качестве десятичного разделителя (европейский формат). Перед обучением мы преобразуем их в числа с плавающей точкой — иначе SDV не сможет корректно обработать эти данные.

3. Создание метаданных

metadata = SingleTableMetadata()
metadata.detect_from_dataframe(real_df)
metadata.save_to_json('metadata.json')

SDV автоматически определяет типы каждой колонки: числовые, категориальные, даты и т.д.. Метаданные сохраняются в JSON-файл — это полезно для воспроизводимости: в следующий раз вы сможете загрузить их, а не пересоздавать заново.

4. Обучение синтезатора

synthesizer = GaussianCopulaSynthesizer(metadata)
synthesizer.fit(real_df)

Синтезатор «изучает» ваши данные: анализирует распределения каждой колонки, корреляции между ними, паттерны пропусков. Этот процесс занимает секунды даже на 10 000 строк.

5. Генерация синтетических данных

synthetic_df = synthesizer.sample(NUM_SYNTHETIC_ROWS)

Метод `sample()` создаёт нужное количество новых записей. В нашем случае мы генерируем столько же строк, сколько было в оригинале. При желании можно увеличить или уменьшить это число — синтезатор умеет масштабировать данные.

6. Оценка качества

diagnostic = DiagnosticReport()
diagnostic.generate(real_df, synthetic_df, metadata.to_dict())

quality = QualityReport()
quality.generate(real_df, synthetic_df, metadata.to_dict())

Этот этап — одна из сильных сторон SDV. Мы проверяем два аспекта:

  • Диагностика (Data Validity + Data Structure) — проверяет, что синтетические данные корректны по типам и структуре. В нашем случае — 100%.
  • Quality Report — оценивает статистическое сходство с реальными данными по двум метрикам:
    • Column Shapes Score (90.77%) — насколько хорошо совпадают распределения отдельных колонок.
    • Column Pair Trends Score (37.71%) — насколько хорошо сохранились попарные зависимости между колонками.

Общий Score 64.24% — это нормальный результат для демонстрационного примера. Для улучшения качества можно переключиться на `CTGANSynthesizer` или увеличить объём обучающих данных.

7. Сохранение в ClickHouse

client.command(f"CREATE TABLE IF NOT EXISTS {SYNTHETIC_TABLE} AS {SOURCE_TABLE}")
client.insert_df(SYNTHETIC_TABLE, synthetic_df)

Финальный шаг: создаём новую таблицу с той же структурой, что и исходная, и заполняем её синтетическими данными. Всё готово — вы можете работать с синтетической копией так же, как с реальной таблицей.


Интеграция с ClickHouse: почему это важно

ClickHouse — популярная колоночная СУБД для аналитики. Генерация синтетических данных прямо в экосистему ClickHouse открывает широкие возможности:

  • Тестирование производительности — можно генерировать датасеты любого размера и проверять, как ClickHouse справляется с нагрузкой.
  • Разработка без доступа к продакшену — разработчики получают рабочую копию данных без риска утечки конфиденциальной информации.
  • Обучение моделей — синтетические данные можно использовать для предварительного обучения ML-моделей, не затрагивая реальные данные.

Альтернативный подход — использование встроенных средств ClickHouse, таких как движок `GenerateRandom`. Однако SDV даёт гораздо более качественные данные, потому что он не просто генерирует случайные значения, а воспроизводит реальные статистические паттерны.


Что дальше: улучшение качества синтеза

Если качество синтеза (особенно Column Pair Trends) вас не устраивает, вот несколько способов его улучшить:

  1. Увеличьте объём обучающих данных — вместо 10 000 строк загрузите 50 000 или 100 000.
  2. Попробуйте CTGANSynthesizer — нейросетевой синтезатор лучше справляется со сложными зависимостями.
  3. Настройте метаданные вручную — если автоматическое определение типов сработало неидеально, вы можете отредактировать `metadata.json` и указать типы явно.
  4. Используйте условную генерацию — SDV позволяет фиксировать значения отдельных колонок и генерировать остальные с учётом этих условий.


Заключение

Мы прошли полный путь: от установки библиотек до готовой синтетической таблицы в ClickHouse. SDV оказался мощным и при этом простым в использовании инструментом — весь процесс уместился в один скрипт на 80 строк.

Главные выводы:

  • SDV — это не просто генератор случайных данных, а интеллектуальный инструмент, который сохраняет статистическую структуру оригинала.
  • `GaussianCopulaSynthesizer` — отличная точка входа: быстрый, прозрачный, даёт хорошее качество.
  • Встроенная оценка качества позволяет объективно понять, насколько синтетика похожа на реальные данные.
  • Интеграция с ClickHouse через `clickhouse-connect` делает процесс полностью автоматизированным.

Синтетические данные открывают новые возможности для разработки, тестирования и анализа данных — без рисков, связанных с работой с реальными данными. А SDV даёт в руки инструмент, который превращает эту идею в реальность за считанные минуты.

ПЫСЫ:

Можно еще попробовать так: CTGANSynthesizer

from sdv.single_table import GaussianCopulaSynthesizer, CTGANSynthesizer

Но надо будет дольше ждать... и заменить тип в строке на

CTGANSynthesizer
uv run sdv_py_v55.py
Загружено 9994 строк
Метаданные сохранены в metadata.json
Начинаем обучение синтезатора...
PerformanceAlert: Using the CTGANSynthesizer on this data is not recommended. To model this data, CTGAN will generate a large number of columns.

Original Column Name   Est # of Columns (CTGAN)
Order Date             1236
Ship Date              1334
Ship Mode              4
Customer Name          793
segment                3
Country/Region         1
region                 4
category               3
Sub-Category           17
Product Name           1849
sales                  5825
quantity               11
discount               12
profit                 7287

We recommend preprocessing discrete columns that can have many values, using 'update_transformers'. Or you may drop columns that are not necessary to model. (Exit this script using ctrl-C)

Все .. комп полетел)) 5 мин... 7 мин... 15... чем то напоминает майнинг))) .... 20... 30... пойду пожалуй спать ...

Follow this blog
Send
Share
Tweet
Pin