资讯动态

表格数据合成实战:从SMOTE插值到CTGAN深度生成的选型与评估

发布时间:2026/10/29 11:25:30 来源:尧图企业网站定制
简介这是一份面向机器学习工程师、数据科研人员及学生的综合项目资源聚焦表格数据合成与质量评估适用于数据稀缺、不平衡分类或隐私保护等场景。方案将生成对抗网络CTGAN、TabDiff与传统过采样技术SMOTE、ADA相结合构建从原始数据分布学习、少数类样本扩充到合成数据生成的完整流程。压缩包共含493个文件大小约75.99MB以CSV数据文件、Python脚本、PNG可视化图表为主并包含JSON配置、Markdown文档及模型文件其中CSV承载原始与合成数据集py实现模型核心逻辑png展示对比与评估图表。目前已有65人学习下载。资源内含可运行的模型代码、实验数据集、SHAP特征分析结果及多种评估指标对比并附环境配置说明与目录结构指引可帮助快速上手生成对抗网络与过采样技术在表格数据上的应用深入理解合成数据的质量评估方法。1. 表格数据合成解决什么问题数据不够用合成又不敢用做风控的同行应该都遇到过这种局面欺诈样本只有几千条正常样本几百万条模型在测试集上 AUC 还行一上线召回率直接崩。数据不够、类别不平衡、隐私受限这些诉求最后都会指向同一个词——表格数据合成。标题里把生成对抗网络CTGAN、TabDiff和经典过采样SMOTE、ADASYN放在同一个项目里不是技术堆砌而是两类完全不同的补数据思路一个是几何插值一个是深度生成。它们各有适用边界合成出来的数据也各有问题。这篇文章按照「原理 → 复现 → 评估 → 踩坑 → 进阶」的顺序把两条路线讲清楚。你可以照着代码跑一遍最小示例也可以跳过代码只看选型判断。目标就一个下次项目里缺数据你知道用哪个工具、怎么评估效果、哪些坑必须绕开。适合正在做风控、营销、医疗分析或任何不平衡数据集建模的算法工程师和数据工程师。2. 从插值到对抗生成SMOTE、ADASYN 与 CTGAN 的底层逻辑2.1 过采样为什么是插值而不是生成SMOTE 的思想极其朴素既然少数类样本太少就在已有的少数类样本之间“造”一些新样本。具体做法是选一个少数类样本 x找到它的 k 个少数类近邻随机选一个邻居 x_n然后在这两个点的连线上按比例取一个新点。x_new x λ × (x_n - x)λ 在 0 到 1 之间随机取值。这个操作本质是线性插值不学习任何分布所以它速度快、可解释强、几乎不会训练崩溃但也决定了它只能处理样本稀疏问题不能创造特征空间之外的信息。ADASYN 在 SMOTE 的基础上改了一件事每个少数类样本附近生成的数量不一样。如果一个样本的 k 近邻里多数类占比高说明这个位置“难学”ADASYN 就在它附近多生成样本让合成数据集中在决策边界附近。两者的共同局限是把特征当成连续值处理。如果你的表里有性别、婚姻状况、地区代码这类离散列原始 SMOTE 会把插值结果取到 0.3 和 0.8 之间生成出“性别0.65”这种不存在的取值。所以实际项目里如果表里有离散列我一般直接用 SMOTENC 而不是 SMOTE。SMOTENC 是 imbalanced-learn 里支持指定离散列的变体它在插值时会保证离散列从邻居的真实取值中采样而不是线性插值。2.2 CTGAN 怎么处理表格数据和图像的本质差异CTGAN 是生成对抗网络在表格数据上的代表性实现。GAN 的基本框架这里不展开重点是它为了解决表格数据做了三个关键设计。第一个是 mode-specific normalization。图像是连续的规则网格表格的连续列往往是多峰分布比如收入列可能集中在 5k 和 50k 两个区间。CTGAN 用高斯混合模型把连续列拆成多个分布模式每个值记录它属于哪个模式以及在模式内的归一化位置生成时反向还原。第二个是 conditional vector。原始 GAN 训练时生成器容易偷懒只生成那些在训练集里出现频率高的离散类少数类被无视。CTGAN 会构造一个条件向量强制生成器在每一批训练里覆盖所有离散类别。第三个是 training-by-sampling配合条件向量使用让每个离散类被抽中的概率与它在真实数据里的占比无关保证训练时每个类别都被充分看到。这三个机制是 CTGAN 的核心也直接对应它的三个超参数log_frequency 控制是否按类别频率的对数采样batch_size 影响模式覆盖度epochs 控制整体训练时长。我在实战里通常先固定 log_frequencyTrue再用小数据集跑通然后再加大 epochs。2.3 TabDiff 的扩散路线与 GAN 的差异标题里把 TabDiff 和 CTGAN 并列为“生成对抗网络相关方法”严格说 TabDiff 不是 GAN而是基于扩散模型的生成方法。它在 2024 年前后被提出核心思路是用连续时间的扩散过程逐步加噪、再逐步去噪从随机噪声中还原出表格数据。扩散模型天然擅长建模高维、复杂的联合分布所以对待表格里离散列和连续列之间复杂的依赖关系TabDiff 通常比 CTGAN 更稳生成样本的多样性也更好。但代价是训练和采样都更慢。CTGAN 训练几百个 epoch 可能只要几分钟TabDiff 的每一步去噪都要完整跑一遍神经网络如果表格列数多、样本量大耗时可能是 CTGAN 的数倍。我个人的选型习惯是样本量小于 5 万、列数小于 30 时先试 CTGAN如果 CTGAN 生成的数据分布在相关性上与原始数据差距明显再换 TabDiff 对比。千万不要一上来就上最重的模型。下面这个表格是三条路线的快速对比也是我每次选型时脑子里过一遍的清单方法类型训练成本离散列支持主要风险SMOTE / ADASYN几何插值极低需用 SMOTENC只能插值无法创造新特征组合CTGANGAN中等原生支持训练不收敛、类别覆盖不全TabDiff扩散模型高原生支持训练慢、采样步数影响质量选型没有绝对正确答案。特征少、样例少、时间紧走 SMOTENC项目周期长、数据质量要求高、下游要上线优先 CTGAN 或 TabDiff。关键是把两条路线都跑一遍用评估指标说话这个我在第 4 章详细展开。3. 把 SMOTE 和 CTGAN 跑起来最小可复现代码与参数设置3.1 环境安装与数据准备先准备一个能跑通的数据集。真实数据集下载不稳定我习惯用 scikit-learn 的 make_classification 构造一个合成表先验证流程再切到业务数据。我用 Python 生成一个 2 万行、8 个特征、少数类占比 5% 的数据集。这里特意加了两个类别特征模拟真实业务表的离散列。import pandas as pd import numpy as np from sklearn.datasets import make_classification from sklearn.model_selection import train_test_split X, y make_classification( n_samples20000, n_features6, n_informative4, n_redundant1, n_clusters_per_class1, weights[0.95, 0.05], random_state42 ) # 额外构造两个离散列模拟业务表的类别特征 df pd.DataFrame(X, columns[fnum_{i} for i in range(6)]) df[cat_1] np.random.choice([A, B, C], sizelen(df), p[0.6, 0.3, 0.1]) df[cat_2] np.random.choice([X, Y], sizelen(df), p[0.7, 0.3]) df[target] y train, test train_test_split(df, test_size0.2, stratifydf[target], random_state42) print(train[target].value_counts())逻辑说明make_classification 的参数 weights[0.95, 0.05] 控制类别不平衡比例n_clusters_per_class 决定每个类别是否聚集为单个簇。随后我把连续特征装进 DataFrame又手动加了 cat_1、cat_2 两个离散列用 np.random.choice 按指定概率生成。train_test_split 里指定 stratify保证训练集和测试集的类别比例一致。这一步有两个关键点。第一先切分再采样这一步顺序错了后面评估全是虚高第二随机种子固定为 42方便你复现和对比实验。如果你用自己的业务数据把 df 换成自己的 DataFrame保留 target 列即可。3.2 用 SMOTENC 处理带有离散列的表格数据很多人直接用 SMOTE 处理全表数据结果离散列被插值生成出一堆不存在的类别。正确做法是用 SMOTENC它需要你显式指定哪些列是离散列。from imblearn.over_sampling import SMOTENC # 指定离散列位置cat_1 在第 6 列cat_2 在第 7 列 categorical_features [6, 7] smote SMOTENC( categorical_featurescategorical_features, sampling_strategyauto, k_neighbors5, random_state42 ) X_train train.drop(target, axis1) y_train train[target] X_res, y_res smote.fit_resample(X_train, y_train) print(原始少数类数量:, (y_train 1).sum()) print(过采样后少数类数量:, (y_res 1).sum())逻辑说明SMOTENC 的 categorical_features 接收的是列索引列表它内部会对这些列做特殊处理插值时离散列从 k 近邻中随机选一个真实值连续列才做线性插值。fit_resample 返回增强后的特征和标签之后可以直接喂给模型训练。参数这里要细说。sampling_strategyauto 表示把少数类补到和多数类一样多。如果你不想补这么多可以传小数比如 0.5表示补到多数类的 50%。k_neighbors5 控制插值参考的近邻数量。数据量大时可以加到 10但要注意如果某个少数类样本周围少数类太少SMOTE 可能报错这时需要调小 k。random_state42 必须固定否则每次过采样结果都不同下游模型效果无法对比。3.3 用 CTGAN 训练并生成合成表格CTGAN 的开源实现由 SDV 团队维护pip 安装后可以直接调用。我以 ctgan 库的独立接口为例因为它的 API 最直观适合先跑通流程。SDV 里的 CTGAN 接口封装更深适合生产级管线原理一致。from ctgan import CTGAN # 离散列直接传原始列名CTGAN 内部会做 one-hot 编码 discrete_columns [cat_1, cat_2] ctgan CTGAN( epochs300, batch_size500, log_frequencyTrue, verboseTrue ) ctgan.fit(train.drop(target, axis1), discrete_columns) # 生成 10000 条合成数据 synthetic ctgan.sample(10000) print(synthetic.head()) print(synthetic[cat_1].value_counts())逻辑说明CTGAN 的 fit 接口接收两个参数——dataframe 和 discrete_columns 列表。它会自动识别连续列并做高斯混合建模离散列则做 one-hot 编码和条件向量处理。训练完调用 sample(10000) 生成指定数量的合成数据输出的 DataFrame 列名和原始数据一致可以直接参与下游任务。epochs 是最关键的超参数。我一般用 300 起步训练集越大、列越多需要的 epoch 越多。如果训练 loss 波动剧烈或生成效果差要么加大 epochs要么先减小 batch_size 再看。batch_size 太小会导致训练不稳定太大则每个 epoch 太慢500 是大多数中小型表格的安全起点。log_frequencyTrue 是必须开的否则高频类别的生成优势会碾压低频类别合成数据里少数类比例会失真。训练完成后建议把模型保存下来避免重复训练ctgan.save(ctgan_model.pkl) loaded CTGAN.load(ctgan_model.pkl) new_samples loaded.sample(5000)这一步的价值在于业务方可能要反复调整合成数据的数量、观察不同样本量下的下游表现不用每次都重新训练。保存模型也方便做版本管理同一个训练配置产生的合成数据才可以横向对比评估指标。4. 合成数据好不好三个指标说了算分布、相关性与下游效用4.1 分布相似性连续列和离散列分别怎么测判断合成数据质量的第一个维度是分布相似性——合成数据的每一列和原始数据同一列的分布是否一致。连续列推荐用 Wasserstein 距离它比 KL 散度更稳定对分布平移有直观的几何意义离散列则直接对比各类别频率的绝对差异即可。我用 scipy 的 wasserstein_distance 实现连续列对比用简单的频率差实现离散列对比。from scipy.stats import wasserstein_distance def evaluate_distribution(real_df, synth_df, discrete_cols): results {} for col in real_df.columns: if col in discrete_cols: # 离散列对比各类别占比差距 real_counts real_df[col].value_counts(normalizeTrue) synth_counts synth_df[col].value_counts(normalizeTrue) all_cats set(real_counts.index) | set(synth_counts.index) diff 0 for cat in all_cats: diff abs(real_counts.get(cat, 0) - synth_counts.get(cat, 0)) results[col] round(diff, 4) else: # 连续列Wasserstein 距离越小越相似 dist wasserstein_distance(real_df[col], synth_df[col]) results[col] round(dist, 4) return results # 用 CTGAN 生成的数据为例 real_data train.drop(target, axis1) dist_results evaluate_distribution(real_data, synthetic, [cat_1, cat_2]) print(dist_results)逻辑说明这个函数遍历所有列离散列用频数差的绝对值求和连续列用 Wasserstein 距离。Wasserstein 距离的含义是两个分布之间需要搬运多少“质量”才能重合数值越小分布越接近。离散列的 diff 则是所有类别占比差距的总和如果合成数据中某个类别的占比和原始数据完全一样diff 为 0。看结果时不要只看绝对数值更有效的方式是横向比较同一数据集分别用 SMOTENC 和 CTGAN 合成然后比较哪个的 Wasserstein 距离更小选更小的那个。因为不同数据集的特征 scale 不同数值无法跨数据集比较但同一数据集内部是公平的。4.2 相关性保持列间相关矩阵差异单列分布一致不意味着多列之间的关联关系被保留了。比如收入列和贷款金额列高度相关如果合成数据把这两列打散成独立分布下游模型就学不到这个关键关系。相关性评估的核心是计算原始数据和合成数据的相关矩阵然后比较两个矩阵的差异。def evaluate_correlation(real_df, synth_df): real_corr real_df.corr() synth_corr synth_df.corr() diff (real_corr - synth_corr).abs().values # 取矩阵上三角的均值避免重复计算 upper_tri diff[np.triu_indices_from(diff, k1)] return round(upper_tri.mean(), 4) corr_diff evaluate_correlation(real_data, synthetic) print(列间相关性差异平均绝对偏差:, corr_diff)逻辑说明real_df.corr() 默认计算 Pearson 相关系数矩阵对于连续列之间是标准的相关关系离散列参与计算时会被当作数值编码相关性含义会弱一些。所以这个函数更适合连续列为主的表格如果离散列很多建议分类型单独算连续对连续用 Pearson离散对离散用 Cramérs V连续对离散用 ANOVA F 值。这里有个常见误区只看相关矩阵差异的均值是不够的。如果某两列在业务上强相关比如年龄和收入你更应该单独看这两个变量在合成数据中的相关系数是否和原始数据一致。均值会被几十个弱相关列稀释掉关键列的偏差反而被掩盖。4.3 下游任务效用TSTR 评估框架分布和相关性是统计视角但业务方最终关心的是用合成数据训练的模型在真实数据上效果如何。TSTRTrain on Synthetic, Test on Real是评估合成数据可用性的标准范式。from sklearn.linear_model import LogisticRegression from sklearn.metrics import f1_score # 真实数据上训练得到的 F1 作为基准 real_model LogisticRegression(max_iter1000, random_state42) real_model.fit(X_train, y_train) y_pred_real real_model.predict(test.drop(target, axis1)) base_f1 f1_score(test[target], y_pred_real) # 合成数据上训练真实测试集上评估 X_synth pd.concat([X_res, synthetic], ignore_indexTrue) y_synth pd.concat([y_res, pd.Series([1] * len(synthetic))], ignore_indexTrue) synth_model LogisticRegression(max_iter1000, random_state42) synth_model.fit(X_synth, y_synth) y_pred_synth synth_model.predict(test.drop(target, axis1)) synth_f1 f1_score(test[target], y_pred_synth) print(真实数据训练 F1:, round(base_f1, 4)) print(合成数据训练 F1:, round(synth_f1, 4)) print(TSTR 比率:, round(synth_f1 / base_f1, 4))逻辑说明这里我用逻辑回归作为下游模型因为它训练快、对数据质量敏感适合做基准测试。TSTR 比率的含义是合成数据能达到真实数据训练效果的几成0.9 以上说明合成质量很好0.7 以下就要回头检查合成过程了。注意一个细节合成数据里我把 CTGAN 生成的样本全部标为少数类target1和 SMOTENC 过采样后的数据合并在一起。实际业务中你可能只使用其中一种但合并使用能充分利用两种方法的互补性。TSTR 评估的关键是测试集必须是没有参与任何合成过程的真实数据所以我在第 3 章先做了 train_test_split这一步的顺序绝对不能乱。5. 避坑指南表格数据合成里五个高频翻车现场5.1 翻车现场SMOTE 把离散列插值出不存在的类别现象SMOTE 处理后性别列出现 0.55、0.72 这类中间值模型训练直接报错或者类别特征被当作连续值参与计算。原因原始 SMOTE 的实现对所有特征统一做线性插值不知道哪些列是离散的。离散列的取值代表类别而非大小插值结果毫无意义。解决换成 SMOTENC显式传入 categorical_features 参数指定离散列。如果用的是 R 语言的 DMwR 包里的 SMOTE同样只支持连续特征需要先把离散列做 one-hot 再插值或者手动对离散列做邻居采样替换。判断列是否离散有一个简单标准列的去重数量少于总样本量的 5%并且取值之间没有大小关系就当作离散列处理。像邮政编码这种虽然大但也是离散列不能插值。5.2 翻车现场先过采样再切分导致数据泄漏现象模型在评估集上 F1 高达 0.95上线后直接跌到 0.5训练和评估结果严重脱节。原因在 train_test_split 之前就做了 SMOTE 或 CTGAN 合成合成过程的“参考样本”包含了测试集的信息。过采样时生成的新样本可能和测试样本高度相似模型在评估时相当于见过答案。解决强制流程顺序——先切分再在训练集上拟合合成器测试集完全不参与。这是我项目里写进组内规范的一条铁律。补充一点CTGAN 也一样fit 只能用训练集sample 出来的数据只能和训练集合并绝对不能把测试集混进 fit 的数据里。5.3 翻车现场CTGAN 训练不收敛生成样本全是同一个模式现象训练 loss 一直震荡生成的样本里某个类别占比超过 90%其他类别几乎消失连续列生成的值集中在某个固定值附近。原因大多数情况是训练参数设置不合适——epochs 太少、batch_size 太大、离散列基数太高导致模式覆盖不足。少数情况是数据本身有问题比如某列有大量缺失值或常数列CTGAN 把常数列也当作分布去建模拖慢训练。解决先检查数据里有没有缺失值和全常数列有就删除或填充然后调小 batch_size 到 100 到 200增大 epochs 到 500log_frequency 设置为 True。如果还没有改善把学习率调低CTGAN 默认的 learning rate 是 2e-4可以试 5e-5。调 GAN 类的模型有点像玄学但核心原则只有一个让生成器和判别器的能力匹配。判别器太强生成器学不到东西生成器太强则会有模式坍塌。用 pac 参数默认为 10可以限制判别器一次看到的样本数变相平衡两者。5.4 翻车现场离散列基数太高one-hot 维度爆炸现象CTGAN 训练内存溢出或者每个 epoch 耗时极长。常见于城市代码几百个类别、用户 ID、订单号这类基数超高的离散列。原因CTGAN 对离散列做 one-hot 编码基数 500 的列会变成 500 维如果表里有 5 个这样的列特征维度直接破千训练代价指数级上升。解决对高基数离散列做业务上的归并——把出现频率低于某个阈值的类别合并为“其他”。阈值一般设置为样本量的 0.1%也就是 1 万条数据里少于 10 次的类别都归并。如果归并之后基数仍然超过 100我建议放弃 CTGAN 改用 TabDiff扩散模型对这种高维稀疏特征的处理更稳定。5.5 翻车现场只看分布相似性忽略下游实际效果现象合成数据的 Wasserstein 距离很小相关性矩阵差异也很小但下游模型 F1 掉得厉害。原因统计指标衡量的是整体分布逼近程度但实际业务场景往往只依赖少数关键特征和它们之间的交互。合成器可能是“平均地”模仿了整个分布但没学到少数关键的模式导致下游模型抓不住真正有价值的信号。解决把 TSTR 评估作为最终验收标准分布指标只作为辅助诊断工具。如果分布指标很好但 TSTR 很差优先检查合成数据里关键业务特征的列间关系比如风控场景里的“金额”和“时间”这两个特征是否保持了原始数据的相关结构。也可以用一个间接方法在原始数据上训练模型看哪些特征权重最大然后单独看这些特征在合成数据中的分布是否一致。6. TabDiff 的调参思路扩散模型该怎么用好TabDiff 和 CTGAN 的核心差异体现在用户感知最强的两个点上训练稳定性和生成速度。CTGAN 的对抗训练在三五轮后可能突然崩掉TabDiff 的扩散过程重建的是加噪还原的确定性路径训练要稳得多。但 TabDiff 的采样过程需要设定去噪步数这个值直接决定生成质量。如果你开始尝试 TabDiff我建议按以下顺序验证它是否适合你的数据集。第一步用默认参数生成一批数据跑一遍 TSTR 评估和 CTGAN 的结果对比。第二步调整去噪步数从 50 步开始对比 100 步和 200 步的 TSTR 指标。去噪步数越多生成样本和真实分布的拟合度越好但耗时线性增长。第三步检查离散列生成质量。TabDiff 对离散列的常见处理方式是先做 one-hot 编码再加噪去噪后取 argmax 还原类别。这个路径比 CTGAN 的 conditional vector 更直接但如果你有高基数离散列一定要观察生成类别中是否出现了真实数据里不存在的组合。实际操作中我的做法是把 CTGAN 和 TabDiff 都跑三遍取 TSTR 比率的中位数对比。单次训练结果波动很大尤其是 CTGAN换一个随机种子可能就从 0.9 掉到 0.75。多次实验取中位数能过滤掉这种随机性。固定随机种子虽然是好习惯但不要只跑一次就下结论这是我在项目里踩过的坑。还有一个容易被忽略的技巧合成数据的数量不是越多越好。CTGAN 生成 10 万条和生成 1 万条的 TSTR 指标经常没有显著差异反而采样和训练时间成倍增加。我用过一个经验法则——合成数据量设为原始训练集的 1 到 2 倍即可除非业务上有隐私替换需求需要覆盖更多场景。最后说一个我个人的习惯表格数据合成项目的核心产物不是合成数据而是评估报告。每次实验记录好原数据统计分布、合成数据统计分布、TSTR 比率、模型版本、随机种子这五个信息后续调参才有依据。没有记录的多组实验等于白做这个教训让我返工过好几次。希望这篇内容能帮你少走一些弯路把合成数据真正用起来。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑