베이지안 네트워크를 알아보기 전에, 먼저 어떻게 쓰이는지 직접 해보도록 하자.
해보고 흥미를 가진 뒤에 개념을 알아볼 예정.
BN은 합성 데이터 관점에서 어떻게 쓰이고 있을까?
논문 하나를 참고하여 한번 해보도록 하자.
Synthetic Data Generation Using Bayesian Networks: A Step-by-Step Guide
In today’s data-driven world, privacy and data security are paramount concerns. As a result, sharing and analyzing sensitive data can be…
medium.com
Abstract
저자는 Python에서 DataSynthesizer library를 이용해서 합성 데이터를 생성했다.
Introduction
BN에 대한 기본적인 설명,
BN = Probabilistic graphical models that represent probabilistic relationships among a set of variables.
쉽게 말해서 내게 어떤 데이터가 있다면, 해당 dataset의 feature들 사이에서의 연관성이나 인과관계를 그래프로 만든 뒤, 그것을 기반으로 확률 정보를 통해 feature의 요소들 값을 뽑아낸다는 것.
실습을 위해 구글 코랩에서 python으로 실행하였다.
1) Environment Setting
# Environment Setting
# Install DataSynthesizer
!pip install DataSynthesizer
# Drive Mount
from google.colab import drive
drive.mount('/content/drive')
2) Data Load
해당 논문에선 CustomerID, Age, Income, Shopping_Habits 총 4개의 feature을 가진 dataset이었다.
Customer ID: unique, 연속형 변수
Age: 연속형 변수
Income: 연속형 변수
Shopping_Habits: categorical (Frequent 혹은 Occasional)
나는 이와 최대한 비슷한 데이터를 kaggle에서 찾아서 .csv형태로 다운받았다.
Retail dataset이고, feature는 총 9개.
https://www.kaggle.com/datasets/mohammadtalib786/retail-sales-dataset?resource=download
Retail Sales Dataset
Unveiling Retail Trends: A Dive into Sales Patterns and Customer Profiles
www.kaggle.com
- Transaction ID: A unique identifier for each transaction, allowing tracking and reference.
- Date: The date when the transaction occurred, providing insights into sales trends over time.
- Customer ID: A unique identifier for each customer, enabling customer-centric analysis.
- Gender: The gender of the customer (Male/Female), offering insights into gender-based purchasing patterns.
- Age: The age of the customer, facilitating segmentation and exploration of age-related influences.
- Product Category: The category of the purchased product (e.g., Electronics, Clothing, Beauty), helping understand product preferences.
- Quantity: The number of units of the product purchased, contributing to insights on purchase volumes.
- Price per Unit: The price of one unit of the product, aiding in calculations related to total spending.
- Total Amount: The total monetary value of the transaction, showcasing the financial impact of each purchase.
총 1000개의 데이터가 들어있다.
이제 이 data를 사용하기 전에, 해당 dataset에서 categorical feature, continuous feature이 무엇인지 확인해주었다.
# feature가 연속형인지 범주형인지 확인
def check_feature_type(df):
feature_types = {'continuous': [], 'categorical': {}}
for column in df.columns:
if df[column].dtype in ['int64', 'float64']:
if df[column].nunique() > 10: # 임의의 기준, 조정 가능
feature_types['continuous'].append(column)
else:
feature_types['categorical'][column] = df[column].nunique()
else:
feature_types['categorical'][column] = df[column].nunique()
return feature_types
# 분석 실행
feature_types = check_feature_type(data)
print("\nFeature 유형 분석:")
print("연속형 features:")
for feature in feature_types['continuous']:
print(f"- {feature}")
print("\n범주형 features:")
for feature, categories in feature_types['categorical'].items():
print(f"- {feature} - {categories}개의 범주")
결과는 다음과 같았다.
Feature 유형 분석:
연속형 features:
- Transaction ID
- Age
- Total Amount
범주형 features:
- Date - 345개의 범주
- Customer ID - 1000개의 범주
- Gender - 2개의 범주
- Product Category - 3개의 범주
- Quantity - 4개의 범주
- Price per Unit - 5개의 범주
이 feature들을 다 사용하면 좋겠지만.. 일단 실습이니 최대한 논문과 유사하게 연속형 3개, 범주형 1개를 제외하고 나머지 feature을 지워주도록 하겠다.
사용할 연속형 features: Transaction ID, Age, Amount, Product Category
# 지정된 feature만 선택하여 새로운 DataFrame 생성
df = data[['Transaction ID', 'Age', 'Total Amount', 'Product Category']].copy()
data_backup = df.copy()
# 구글 드라이브에 CSV 파일로 저장
df.to_csv('구글경로/retail_sales_dataset_newVer.csv', index=False)

이제 df와 백업 df를 저장해놨다면, parameter들을 지정해보도록 하자.
3) Define Parameter
# Specify categorical attributes
categorical_attributes = {'Product Category': True}
# Define privacy settings
epsilon = 0.1
degree_of_bayesian_network = 2
num_tuples_to_generate = 1000
epsilon: 차등 프라이버시를 위한 epsilon 값.
degree_of_bayesian_networtk: 베이지안 네트워크의 복잡도. (이 값이 크면 복잡한 관계를 모델링 가능, but 시간 소모량은 높아지고 과적합 위험이 있다)
num_tuples_to_generate: 만들 합성 데이터의 개수
4) Declare Model
input_data_file='retail_sales_dataset_newVer.csv'
# Initialize DataDescriber with category threshold
describer = DataDescriber(category_threshold=5)
# Describe the dataset to create a Bayesian network
describer.describe_dataset_in_correlated_attribute_mode(dataset_file=input_data_file,
epsilon=epsilon,
k=degree_of_bayesian_network,
attribute_to_is_categorical=categorical_attributes
)
DataDescriber 객체: 원본 데이터셋의 특성을 학습, 이를 바탕으로 베이지안 네트워크를 구축하는 객체
(여기서 category_threshold=5의 의미는 5개의 값보다 적은 카테고리를 가진 feature는 categorical feature로 보겠다는 의미)

이제 이렇게 되면 BN이 생성된다. (Feature들을 보고, 모델이 알아서 DAG를 만들게 된다)

부모와 자식이 누군지 다 보여주는 그래프.

5) Generate Synthetic Data
# DataDescriber를 사용하여 데이터셋 설명 후 파일로 저장
description_file = 'retail_data_description.json'
describer.save_dataset_description_to_file(description_file)
generator = DataGenerator()
generator.generate_dataset_in_correlated_attribute_mode(num_tuples_to_generate, description_file)
# Save synthetic data to a CSV file
synthetic_data_file = 'synthetic_retail_data.csv'
generator.save_synthetic_data(synthetic_data_file)
실행하고 나면, 다음과 같이 synthetic data가 생기는 것을 볼 수가 있다.

이때 description_file이 왜 필요하나면, 해당 json은 원본 데이터의 특성을 캡쳐하고 이를 바탕으로 새로운 합성 데이터를 생성하는 데 필수적인 요소이기 때문이다.
DataSynthetsizer을 사용하기 위해 반드시 필요한 parameter이다!
6) Evaluate the Synthetic Data
일단 두 개의 데이터 .csv를 합쳐준다.
# Load synthetic data
df_synthetic = pd.read_csv(synthetic_data_file)
df_original = data_backup # Assuming you backed up the original data
# Merge original and synthetic data based on Transaction ID
merged_df = pd.merge(df_original, df_synthetic, on='Transaction ID', how='inner')
Transaction ID를 기준으로, 두 데이터를 붙여준다.

1. 각 feature에 대한 항목 비교
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
# 1. Age_x와 Age_y의 비교
plt.figure(figsize=(10, 6))
sns.histplot(data=merged_df, x='Age_x', kde=True, color='blue', label='Original')
sns.histplot(data=merged_df, x='Age_y', kde=True, color='red', label='Synthetic')
plt.title('Distribution of Age: Original vs Synthetic')
plt.legend()
plt.show()
# KS 테스트로 두 분포의 유사성 확인
ks_statistic, p_value = stats.ks_2samp(merged_df['Age_x'], merged_df['Age_y'])
print(f"Age KS Test: statistic={ks_statistic:.4f}, p-value={p_value:.4f}")
# 2. Total Amount_x와 Total Amount_y의 비교
plt.figure(figsize=(10, 6))
sns.histplot(data=merged_df, x='Total Amount_x', kde=True, color='blue', label='Original')
sns.histplot(data=merged_df, x='Total Amount_y', kde=True, color='red', label='Synthetic')
plt.title('Distribution of Total Amount: Original vs Synthetic')
plt.legend()
plt.show()
ks_statistic, p_value = stats.ks_2samp(merged_df['Total Amount_x'], merged_df['Total Amount_y'])
print(f"Total Amount KS Test: statistic={ks_statistic:.4f}, p-value={p_value:.4f}")
# 3. Product Category_x와 Product Category_y의 비교
plt.figure(figsize=(12, 6))
merged_df['Product Category_x'].value_counts(normalize=True).plot(kind='bar', alpha=0.5, label='Original')
merged_df['Product Category_y'].value_counts(normalize=True).plot(kind='bar', alpha=0.5, label='Synthetic')
plt.title('Distribution of Product Category: Original vs Synthetic')
plt.legend()
plt.show()
# 카이제곱 검정
chi2, p_value, _, _ = stats.chi2_contingency([merged_df['Product Category_x'].value_counts(),
merged_df['Product Category_y'].value_counts()])
print(f"Product Category Chi-square Test: statistic={chi2:.4f}, p-value={p_value:.4f}")
KS 테스트(Kolmogorov-Smirnov 테스트): 두 개의 연속 확률 분포가 서로 같은지 비교하는 통계적 검정 방법
p-value > 0.05: 두 분포가 통계적으로 유의미하게 다르지 않다 (= 같다)
p-value < 0.05: 두 분포가 다르다
카이제곱 검정(Chi-squre 테스트): 두 개의 범주형 변수들 간의 관련성을 검정하는 방법
이것 또한 두 분포의 차이가 p-value에 달려있는데, 기준은 KS 테스트와 같음.

Age KS Test: statistic=0.0360, p-value=0.5356
두 분포가 다르지 않다고 봄 = 잘 모방하고 있다.

Total Amount KS Test: statistic=0.5005, p-value=0.0000
상당히 다르다 - synthetic은 좀 더 골고루 나이를 주려고 했던 것 같음.

Product Category Chi-square Test: statistic=1.0485, p-value=0.5920
두 분포가 다르지 않다 = 잘 모방하고 있다
2. 두 feature 사이의 연관성

좌측이 원데이터의 total amount - age 사이의 관계.
우측이 합성데이터의 total amount - age 사이의 관계.
Correlation (Age vs Total Amount): Original=-0.0603, Synthetic=0.0047
원데이터는 특정 금액대에 데이터가 분포되어 있으나.. synthetic은 난리났다.
=> 반영 잘 못했다.

좌측(원본): 각 카테고리별로 총액의 분포가 다르다. clothing이 가장 낮은 중앙값을, Electronics가 가장 높은 중앙값을 보인다.
우측(합성): Beauty의 중앙값이 엄청 증가했다.
=> 반영 잘 못했다.

좌측(원본): 모든 제품 카테고리에서 연령 분포가 비슷함
우측(합성): 원본과 유사하게 카테고리별 연령 분포가 비슷함. 다만 Clothing 카테고리의 중앙값이 약간 낮아졌다.
=> 생각보다 잘 반영하는 것 같다.
7) Conclusion
- 연령과 제품 카테고리의 관계는 비교적 잘 보존되었다.
- 하지만 총액과 관련된 특성(특정 금액대 집중, 제품 카테고리별 총액 분포)은 제대로 반영되지 않았다.
- 연령과 총액의 관계도 변화했다.
ML에서 parameter tuning이 중요하다는 것을 알았으니, BN에서도 parameter을 하나씩 수정해보면서 다시 생성해봐야겠다.
이후에 감을 좀 잡으면, 이제 내가 실제로 사용할 데이터로도 한번 돌려보리라..
'AI' 카테고리의 다른 글
| [논문 리뷰] 합성 데이터(EHR 데이터)에서의 GAN 정리 (0) | 2024.09.10 |
|---|---|
| [논문리뷰] 의료 데이터에 적합한 medGAN (method review) (1) | 2024.09.03 |
| [딥러닝] Numpy Basic Concepts (0) | 2024.04.16 |
| [딥러닝] 기초 - Learning Curves 이해하기 (0) | 2024.04.13 |
| [딥러닝] Feature Scaling (0) | 2024.04.11 |