如何赢得-Mostly-AI-合成数据挑战

如何赢得“Mostly AI”合成数据挑战

原文:towardsdatascience.com/how-i-won-the-mostly-ai-synthetic-data-challenge/

我最近参加了Mostly AI Prize并赢得了 FLAT 和 SEQUENTIAL 数据挑战。这次比赛是一次极好的学习经历,在这篇文章中,我想分享一些关于我获胜方案的想法。

比赛情况

比赛的目标是生成一个具有与源数据集相同统计特性的合成数据集,而不复制数据。

图片

来源:www.mostlyaiprize.com/

比赛分为两个独立挑战:

  1. FLAT 数据挑战:生成具有 80 列的 100,000 条记录。

  2. SEQUENTIAL 数据挑战:生成 20,000 个记录序列(组)。

为了衡量合成数据的质量,比赛使用了总体准确率指标。这个分数通过 L1 距离来衡量合成数据和源分布对于单列(单变量)、成对列(双变量)和三列(三变量)之间的相似性。此外,还使用了如DCR(最近记录距离)NNDR(最近邻距离比)这样的隐私指标,以确保提交的数据不是仅仅过拟合或复制训练数据。

图片

FLAT 挑战的训练数据集样本。图片由作者提供。

解决方案设计

初始目标是我创建多个不同最先进模型的集成,并合并它们生成的数据。我尝试了多种不同的模型,但结果并没有像我希望的那样有显著提升。

我调整了我的方法,并专注于后处理。首先,我从Mostly AI SDK训练了一个单一的生成模型,而不是生成提交所需的样本数量,我过采样以创建一个大量的候选样本池。从这个池中,我然后选择最终输出,使其更接近源数据集的统计特性。

这种方法使得排行榜上的分数有了显著提升。对于 FLAT 数据挑战,模型的原始合成数据得分为约 0.96,但经过后处理,分数提升到了0.992。我使用这个方法的修改版用于 SEQUENTIAL 数据挑战,也取得了类似的改进。

我为 FLAT 挑战的最终流程包括三个主要步骤:

  1. 迭代比例拟合(IPF)选择一个超大规模的高质量子集。

  2. 贪婪剪枝通过移除最不匹配的样本来将子集缩减到目标大小。

  3. 迭代优化以通过交换样本来提高最终数据集的匹配度。

图片

每个后处理步骤对 FLAT 挑战最终准确度得分的影响。图由作者提供。

第 1 步:迭代比例拟合(IPF)

我的后处理流程的第一步是从过采样池(2.5 百万生成的行)中获取一个强大的初始子集。为此,我使用了迭代比例拟合(IPF)

IPF 是一种经典的统计算法,用于调整样本分布以匹配已知的边缘集。在这种情况下,我希望合成数据的双变量(两列)分布与原始数据相匹配。我还测试了单变量和三变量分布,但发现专注于双变量关系在计算上既快又提供了最佳性能。

下面是它是如何工作的:

  1. 我使用互信息识别了训练数据中5,000 个最相关的列对。这些是需要保留的最重要关系。

  2. IPF 随后计算了 2.5 百万个合成行的分数权重。这些权重通过迭代调整,以确保合成池中双变量分布的加权和与训练数据中的目标分布相匹配。

  3. 最后,我使用了一种期望四舍五入方法将这些分数权重转换为每个行应该被选择的整数计数。这导致了一个超集,包含 125,000 行(所需大小的 1.25 倍),并且已经具有非常强的双变量准确性。

IPF 步骤为下一阶段提供了一个高质量的开端。

第 2 步:修剪

从 IPF 生成 125,000 行的超集是一个有意的选择,这使得可以执行额外的修剪步骤来移除不匹配的样本。

我使用了一种贪婪方法,该方法迭代地计算当前子集中每行的“误差贡献”。那些对目标分布的统计距离贡献最大的行被识别并移除。这个过程会重复进行,直到只剩下 100,000 行,确保最差的 25,000 行被丢弃。

第 3 步:优化(交换)

最后一步是一个迭代优化过程,通过从更大的未使用数据池(剩余的 2.4 百万行)中交换行来替换子集中的行。

在每次迭代中,算法:

  1. 在当前 100k 子集中识别最差的行(那些对 L1 误差贡献最大的行)。

  2. 在外部池中寻找最佳替换候选者,如果替换进去会减少 L1 误差。

  3. 如果交换导致整体得分更好,则执行交换。

由于合成样本的准确性已经相当高,因此此过程带来的额外收益相当小。

适应序列挑战

SEQUENTIAL 挑战需要类似的方法,但有两个变化。首先,样本由几行组成,通过组 ID 连接。其次,竞赛指标增加了一个连贯性的度量。这意味着不仅统计分布需要匹配,事件的序列也需要与源数据集相似。

SEQUENTIAL 挑战的训练数据集样本。图片由作者提供。

我的后处理流程被调整为处理组并优化连贯性:

  1. 基于连贯性的预选择:在优化统计准确性之前,我运行了一个专门的细化步骤。该算法迭代地交换整个组(序列),以特别匹配原始数据的连贯性指标,例如“每个序列的独特类别分布”和“每个类别的序列”。这确保了我们继续后处理,保持良好的序列结构。

  2. 细化(交换):为了连贯性选出的 20,000 个组随后经历了与平坦数据相同的统计细化过程。算法将整个组与池中更好的组进行交换,以最小化单变量、双变量和三变量分布的 L1 误差。一个秘密成分是包括“序列长度”作为一个特征,因此组长度也在交换中考虑。

这种两阶段方法确保了最终数据集在统计准确性和序列连贯性方面都很强。有趣的是,对于平坦数据效果很好的基于 IPF 的方法,在序列挑战中却不太有效。因此,我移除了它,将计算时间集中在连贯性和交换算法上,这产生了更好的结果。

让它变得快速:关键优化

后处理策略本身计算成本很高,使其在竞赛时间限制内运行本身就是一项挑战。为了成功,我依赖几个关键优化。

首先,我在可能的情况下减少了数据类型,以处理大量的样本数据池而不会耗尽内存。将大型矩阵的数值类型从 64 位改为 32 或 16 位,可以大大减少内存占用。

其次,当更改数据类型不足以解决问题时,我使用了 SciPy 中的稀疏矩阵。这项技术使我能够以极低的内存效率方式存储每个样本的统计贡献。

最后,核心细化循环涉及大量的专业计算,其中一些在numpy中非常慢。为了克服这一点,我使用了<strong>numba</strong>。通过将代码中的瓶颈提取到具有@numba.njit装饰器的专用函数中,Numba 自动将它们转换为高度优化的机器代码,其运行速度与 C 语言相当。

这里是一个例子,说明我需要加快稀疏矩阵中行求和的速度,这是原始 NumPy 版本中的主要瓶颈

import numpy as np
import numba

# This can make the logic run hundreds of times faster.
@numba.njit
def _rows_sum_csr_int32(data, indices, indptr, rows, K):
    """
    Sum CSR rows into a dense 1-D vector without creating
    intermediate scipy / numpy objects.
    """
    out = np.zeros(K, dtype=np.int32)
    for r in rows:
        start = indptr[r]
        end = indptr[r + 1]
        for p in range(start, end):
            out[indices[p]] += data[p]
    return out

然而,Numba 并非万能的银弹;它对于数值密集型和循环密集型的代码很有帮助,但对于大多数计算来说,坚持使用矢量化 NumPy 操作更快、更简单。我建议你只有在 NumPy 方法无法达到所需速度时才尝试使用它。

最后的想法

每个挑战的前五名提交。来源:github.com/mostly-ai/the-prize-eval/

尽管机器学习模型正变得越来越强大,但我认为对于数据科学家试图解决的多数问题,关键因素往往不在于模型本身。当然,一个强大的模型是解决方案的重要组成部分,但预处理和后处理同样重要。对于这些挑战,一个专门针对评估指标的后处理流程引导我找到了获胜的解决方案,而且无需任何额外的机器学习。

在这个挑战中,我学到了很多,我想感谢Mostly AI和评委们为组织这场精彩的竞赛所做的大量工作。

我为这两个挑战的代码和解决方案都是开源的,可以在以下链接找到:

posted @ 2026-03-28 09:36  绝不原创的飞龙  阅读(0)  评论(0)    收藏  举报