Proyecto2_Deep_Learning / data_generation.py
luisejdm's picture
Update data_generation.py
91ce837 verified
import torch
import pandas as pd
from sdv.single_table import CTGANSynthesizer
from scipy import stats
import numpy as np
# Patch torch.load to remap MPS tensors to CPU for environments without Apple Silicon
_original_torch_load = torch.load
def _cpu_map_load(*args, **kwargs):
kwargs.setdefault('map_location', 'cpu')
return _original_torch_load(*args, **kwargs)
torch.load = _cpu_map_load
def generate_synthetic_training_data(n=30_000):
"""Generates synthetic training data using pre-trained CTGAN models for each credit score category.
Args:
n (int, optional): The number of samples to generate for each category. Defaults to 30_000.
Returns:
pd.DataFrame: The generated synthetic training data.
"""
good_generator = CTGANSynthesizer.load("models/v4/synth_good.pkl")
poor_generator = CTGANSynthesizer.load("models/v4/synth_poor.pkl")
standard_generator = CTGANSynthesizer.load("models/v4/synth_standard.pkl")
synth_good = good_generator.sample(n)
synth_poor = poor_generator.sample(n)
synth_standard = standard_generator.sample(n)
full_data = pd.concat([synth_good, synth_poor, synth_standard], ignore_index=True)
shuffled_data = full_data.sample(frac=1).reset_index(drop=True)
return shuffled_data
def evaluate_synthetic_data(real_df, synthetic_df,
categorical_cols=None, numeric_cols=None):
if numeric_cols is None:
numeric_cols = real_df.select_dtypes(include="number").columns.tolist()
if categorical_cols is None:
categorical_cols = real_df.select_dtypes(include="object").columns.tolist()
# ── KS Test ───────────────────────────────────────────────────────────
ks_results = []
for col in numeric_cols:
stat, p_value = stats.ks_2samp(
real_df[col].dropna(),
synthetic_df[col].dropna()
)
ks_results.append({
"column" : col,
"ks_stat": round(stat, 4),
"p_value": round(p_value, 4),
"pass" : p_value > 0.05
})
ks_df = pd.DataFrame(ks_results)
# ── Chi-Square Test ───────────────────────────────────────────────────
chi_results = []
for col in categorical_cols:
real_counts = real_df[col].value_counts()
synth_counts = synthetic_df[col].value_counts()
all_cats = real_counts.index.union(synth_counts.index)
real_freq = real_counts.reindex(all_cats, fill_value=0)
synth_freq = synth_counts.reindex(all_cats, fill_value=0)
n = real_freq.sum()
f_exp = (real_freq / real_freq.sum()) * n
f_obs = (synth_freq / synth_freq.sum()) * n
stat, p_value = stats.chisquare(f_obs=f_obs, f_exp=f_exp)
chi_results.append({
"column" : col,
"chi_stat": round(stat, 4),
"p_value" : round(p_value, 4),
"pass" : p_value > 0.05
})
chi_df = pd.DataFrame(chi_results)
# ── Correlation Matrix ────────────────────────────────────────────────
real_corr = real_df[numeric_cols].corr()
synth_corr = synthetic_df[numeric_cols].corr()
corr_diff = (real_corr - synth_corr).abs()
upper_idx = np.triu_indices_from(corr_diff.values, k=1)
mean_corr_diff = corr_diff.values[upper_idx].mean()
ks_pass_rate = ks_df["pass"].mean()
mean_ks = ks_df["ks_stat"].mean()
chi_pass_rate = chi_df["pass"].mean() if not chi_df.empty else None
return ks_pass_rate, mean_ks, chi_pass_rate, mean_corr_diff
def data_evaluation(
real_list, synthetic_list, class_names=None, categorical_cols=None, numeric_cols=None
):
if class_names is None:
class_names = ["good", "poor", "standard"]
summary_rows = []
for cls, real_df, syn_df in zip(class_names, real_list, synthetic_list):
ks_pass_rate, mean_ks, chi_pass_rate, mean_corr_diff = evaluate_synthetic_data(
real_df = real_df,
synthetic_df = syn_df,
categorical_cols = categorical_cols,
numeric_cols = numeric_cols
)
summary_rows.append({
"class" : cls,
"ks_pass_rate" : round(ks_pass_rate, 4),
"mean_ks_stat" : round(mean_ks, 4),
"chi_pass_rate" : round(chi_pass_rate, 4) if chi_pass_rate is not None else None,
"mean_corr_diff": round(mean_corr_diff, 4)
})
return pd.DataFrame(summary_rows).set_index("class")