Programming Differential Privacy第十五章Synthetic Data合成数据

Synthetic Data合成数据

1.学习目标

  1. 描述差异私有合成数据的概念,并解释其有用的原因
  2. 定义用于生成合成数据的简单合成表示
  3. 定义边距并实现计算它们的代码
  4. 实现简单的差分私有算法以生成低维合成数据
  5. 描述生成高维合成数据的挑战

在本节中,我们将研究使用差分隐私算法生成合成数据的问题。

严格来说,这种算法的输入是原始数据集,其输出是具有相同形状的合成数据集(即相同的列集和相同的行数)。此外,我们希望合成数据集中的值与原始数据集中的相应值具有相同的属性

例如,如果我们将美国人口普查数据集作为原始数据,那么我们希望我们的合成数据具有与原始数据相似的参与者年龄分布,并保留列之间的相关性(例如年龄和职业之间的联系)。

大多数用于生成此类合成数据的算法依赖于原始数据集的合成表示,原始数据集不具有与原始数据相同的形状,但允许回答关于原始数据的查询。

例如,如果我们只关心年龄范围的查询,那么我们可以生成一个年龄直方图——原始数据中每个可能年龄的参与者数量-并使用直方图来回答查询。该直方图是一种适合回答某些查询的合成表示,但它与原始数据的形状不同,因此它不是合成数据。

一些算法简单地使用合成表示来回答查询。其他人使用合成表示来生成合成数据。我们将研究一种合成表示——直方图——以及几种从中生成合成数据的方法。

2.Synthetic Representation: a Histogram合成表示:直方图

我们已经看到很多直方图,自从并行组合被广泛应用,他们变成差分隐私分析的主要内容。我们还看到了范围查询的概念,尽管我们没有经常使用这个名称。

作为获取合成数据的第一步,我们将为原始数据集的一列设计一个合成表示形式,该列能够回答范围查询。

范围查询计算数据集中值位于给定范围内的行数。例如,"有多少参与者年龄在 21 岁到 33 岁之间?"是一个范围查询。

def range_query(df, col, a, b):
    return len(df[(df[col] >= a) & (df[col] < b)])

print(range_query(adult, 'Age', 21, 33))

9878

我们可以定义一个直方图查询,为0到100之间的每个年龄段定义一个柱状图仓,并使用范围查询计算每个仓中的人数。结果看起来调用plt.hist数据的输出。历史记录-因为我们基本上已经手动计算了相同的结果。

bins = list(range(0, 100))
counts = [range_query(adult, 'Age', b, b+1) for b in bins]
plt.xlabel('Age')
plt.ylabel('Number of Occurrences')
plt.bar(bins, counts)
plt.show()

在这里插入图片描述
我们可以使用这些直方图结果作为原始数据的合成表示!要回答范围查询,我们可以将范围内的条柱的所有计数相加

print(range_query_synth(counts, 21, 33))

9878

请注意,无论是对原始数据还是对合成表示进行范围查询,我们都得到了完全相同的结果。我们没有丢失原始数据集中的任何信息(至少是为了回答随时间变化的范围查询)。

3.Adding Differential Privacy添加差分隐私

我们可以很容易地使我们的合成数据满足差分隐私。我们可以将拉普拉斯噪声分别添加到直方图中的每个计数中,通过并行组合,这满足𝜖-差分隐私。

我们可以使用与以前相同的函数,使用我们的差分隐私合成表示来回答范围查询

通过后处理,这些结果还满足ϵ-差分隐私

此外,由于我们依赖于后期处理,因此我们可以根据需要回答任意数量的查询,而不会产生额外的隐私成本

epsilon = 1
dp_syn_rep = [laplace_mech(c, 1, epsilon) for c in counts]
print(range_query_synth(dp_syn_rep, 21, 33))

9879.625148741274

结果有多准确?对于小范围,我们从合成表示中得到的结果与我们通过将拉普拉斯机制直接应用于我们想要回答的范围查询的结果而得到的结果具有非常相似的准确性。例如:

范围越大,计数越大,因此我们希望误差会有所改善。

我们已经一次又一次地看到了这一点——更大的群体意味着更强的信号,这会导致更低的相对误差

通过拉普拉斯机制,我们可以准确地看到这种行为。然而,通过我们的合成表示,我们将来自许多较小组的噪声结果相加在一起-因此,随着信号的增长,噪声也会随之增长!

因此,无论范围的大小如何,我们在使用合成表示时都会看到大致相同的相对误差大小,与拉普拉斯机制恰恰相反!

这种差异表明了我们的合成表示的缺点
它可以回答其覆盖范围内的任何范围查询,但可能无法提供与拉普拉斯机制相同的精度
我们的合成表示的主要优点是能够回答无限多的查询,而无需额外的隐私预算;主要缺点是精度的损失

true_answer = range_query(adult, 'Age', 30, 31)
print('给每个bins加laplace:Synthetic representation error: {}'.format(pct_error(true_answer, range_query_synth(dp_syn_rep, 30, 31))))
print('给每个bins加laplace:Synthetic representation error: {}'.format(pct_error(true_answer, range_query_synth(counts, 30, 31))))
print('给整体结果加laplace:Laplace mechanism error: {}'.format(pct_error(true_answer, laplace_mech(true_answer, 1, epsilon))))
print('--------')
true_answer = range_query(adult, 'Age', 30, 71)
print('给每个bins加laplace:Synthetic representation error: {}'.format(pct_error(true_answer, range_query_synth(dp_syn_rep, 30, 71))))
print('给整体结果加laplace:Laplace mechanism error: {}'.format(pct_error(true_answer, laplace_mech(true_answer, 1, epsilon))))

给每个bins加laplace:Synthetic representation error: 0.1710780390938941
给每个bins加laplace:Synthetic representation error: 0.0
给整体结果加laplace:Laplace mechanism error: 0.05817208752574446
--------
给每个bins加laplace:Synthetic representation error: 0.024854856598939393
给整体结果加laplace:Laplace mechanism error: 0.0009579983848163375

Process finished with exit code 0

4.Generating Tabular Data生成表格数据

下一步是从我们的合成表示到合成数据。为了做到这一点,我们希望将我们的合成表示视为一种概率分布,它估计了原始数据的基础分布,并从中采样。因为我们只考虑了一列,而忽略了所有其他列,所以这被称为边际分布(特别是单向边际分布)。

我们的策略很简单:我们对每个直方图条柱都有计数;我们将对这些计数进行规范化,使它们的总和为 1,然后将它们视为概率。

dp_syn_rep_nn = np.clip(dp_syn_rep, 0, None)
syn_normalized = dp_syn_rep_nn / np.sum(dp_syn_rep_nn)
print(np.sum(syn_normalized))

1.0

请注意,如果我们绘制归一化计数-我们现在可以将其视为每个对应直方图仓的概率,因为它们的总和为1-我们会看到一个看起来非常像原始直方图的形状(反过来,它看起来很像原始数据的形状)。这一切都是意料之中的——除了规模,这些概率只是计数。

plt.xlabel('Age')
plt.ylabel('Probability')
plt.bar(bins, syn_normalized)
plt.show()

概率分布图,加权和是1
在这里插入图片描述
最后一步是根据这些概率生成新的样本。我们可以使用np.random.choice,这允许传递与第一参数中给出的选择相关联的概率列表(在p参数中)。

它精确地实现了我们采样任务所需的加权随机选择我们可以生成任意多的样本,而不需要额外的隐私成本,因为我们已经将我们的计数进行了不同程度的私有化。

def gen_samples(n):
    return np.random.choice(bins, n, p = syn_normalized)
syn_data = pd.DataFrame(gen_samples(5), columns=['RandomAge'])
print(syn_data)

   		RandomAge
0         26
1         41
2         55
3         50
4         45

我们以这种方式生成的样本将按照与原始数据相同的底层分布进行大致分布——我们希望如此。这意味着我们可以使用生成的合成数据来回答我们可以使用原始数据回答的相同查询。特别是,如果我们在一个大的合成数据集中绘制年龄直方图,我们将看到与原始数据相同的形状。

syn_data = pd.DataFrame(gen_samples(10000), columns=['RandomAge'])
plt.xlabel('Age')
plt.ylabel('Number of Occurrences')
plt.hist(syn_data['RandomAge'], bins = bins)
plt.show()

分布和上边的概率分布图很像的
在这里插入图片描述
我们还可以回答我们过去看到的其他查询,如平均值和范围查询:

print('Mean age, synthetic: {}'.format(np.mean(syn_data['RandomAge'])))
print('Mean age, true answer: {}'.format(np.mean(adult['Age'])))
print('Percent error: {}'.format(pct_error(np.mean(syn_data['RandomAge']), np.mean(adult['Age']))))
print('--------')
print('Mean age, synthetic: {}'.format(range_query(syn_data, 'RandomAge', 20, 65)))
print('Mean age, true answer: {}'.format(range_query(adult, 'Age', 20, 65)))
print('Percent error: {}'.format(pct_error(range_query(adult, 'Age', 20, 65),
                                           range_query(syn_data, 'RandomAge', 20, 65))))
                                           
Mean age, synthetic: 38.8055
Mean age, true answer: 38.58164675532078
Percent error: 0.57685958093368
--------
Mean age, synthetic: 9087
Mean age, true answer: 29568
Percent error: 69.2674512987013

Process finished with exit code 0

我们的均值查询具有相当低的误差尽管仍然比我们直接应用拉普拉斯机制所能达到的误差要大得多)。

然而,我们的范围查询有很大的误差!这只是因为我们没有完全匹配原始数据的形状。我们只生成了10000个样本,原始数据集有30000多行。(倍数差了三倍,因为我们生成的10000行都是按照原来的概率分布的)

我们可以执行额外的差分隐私查询,以确定原始数据中的行数,然后生成具有相同行数的新合成数据集,这将改善我们的范围查询结果。

n = laplace_mech(len(adult), 1, 1.0)
syn_data = pd.DataFrame(gen_samples(int(n)), columns=['RandomAge'])
print('--------')
print('随机生成样本数量和原数据集数量一样以后:')
print('Mean age, synthetic: {}'.format(range_query(syn_data, 'RandomAge', 20, 65)))
print('Mean age, true answer: {}'.format(range_query(adult, 'Age', 20, 65)))
print('Percent error: {}'.format(pct_error(range_query(adult, 'Age', 20, 65),
                                           range_query(syn_data, 'RandomAge', 20, 65))))

--------
随机生成样本数量和原数据集数量一样以后:
Mean age, synthetic: 29548
Mean age, true answer: 29568
Percent error: 0.06764069264069264

现在我们看到了我们预期的更低的误差。

5.Generating More Columns生成更多列

到目前为止,我们已经生成了与原始数据集的行数相匹配的合成数据,对于回答有关原始数据的查询非常有用,但它只有一列!我们如何生成更多的列?

考虑多列问题。

我们可以对每个k 列重复上面遵循的过程(生成k 个1 向边际【1-way marginals】),并得出k 单独的合成数据集,每个数据集都有一列。然后,我们可以将这些数据集分解在一起,以构造具有k列的单个数据集

这种方法很简单,但由于我们单独考虑每一列,我们将丢失原始数据中存在的列之间的相关性

例如,数据中可能存在年龄和职业相关的情况,数据中可能存在年龄和职业相关的情况(例如,经理更可能年长而不是年轻);如果我们单独考虑每一列,我们会得到正确的18岁经理人数和经理人数,但我们可能对18岁经理的人数大错特错。

另一种方法是同时考虑多个列。例如,我们可以同时考虑年龄和职业,统计有多少18岁的经理,有多少19岁的经理等等。

该修正过程的结果是双向边际分布(2-way marginal distribution)(个人理解二维边际分布)。我们最终会考虑年龄和职业的所有可能组合——这正是我们之前构建应急表时所做的!例如:

ct = pd.crosstab(adult['Age'], adult['Occupation'])
print(ct.head())

在这里插入图片描述
现在我们可以完全像以前那样做了——将噪声添加到这些计数中,然后将它们归一化,并将它们视为概率
现在,每个计数都对应一对值——年龄和职业——因此,当我们从构建的分布中进行采样时,我们将同时获得这两个值

dp_ct = ct.applymap(lambda x: max(laplace_mech(x, 1, 1), 0))
dp_vals = dp_ct.stack().reset_index().values.tolist()
probs = [p for _,_,p in dp_vals]
vals = [(a,b) for a,b,_ in dp_vals]
probs_norm = probs / np.sum(probs)
list(zip(vals, probs_norm))[0]

((17, 'Adm-clerical'), 0.000776459023220336)

检查概率的第一个元素,我们发现我们有0.07%的机会生成一个代表17岁文员的行。

现在,我们已准备好生成一些行!我们将首先在vals列表中生成一个索引列表,然后通过索引到vals;我们必须这样做,因为np.random.choice不会接受第一个参数中的元组列表

indics = range(0, len(vals))
n = laplace_mech(len(adult), 1, 1.0)
gen_indices = np.random.choice(indics, int(n), p = probs_norm)  # 这里传递的是vals的下标
syn_data = [vals[i] for i in gen_indices]

syn_df = pd.DataFrame(syn_data, columns=['Age', 'Occupation'])
print(syn_df.head())

   Age       Occupation
0   48            Sales
1   17  Farming-fishing
2   55     Craft-repair
3   23            Sales
4   37     Tech-support

Process finished with exit code 0

同时考虑两列的缺点是我们的精度会更低。当我们向正在考虑的集合中添加更多列时(即,构建一个n 向边际,增加n 的值),我们看到与对列联表相同的效果 - 每个计数变小,(因为同时满足多列的行数肯定少了啊)因此信号相对于噪声变小,并且我们的结果不那么准确。

我们可以通过在新的合成数据集中绘制年龄直方图来看到这种影响;请注意,它的形状大致正确,但与原始数据或年龄列本身所用的不同私人计数相比,它不够平滑

bins = list(range(0, 100))
plt.xlabel('Age')
plt.ylabel('Number of Occurrences')
plt.hist(syn_df['Age'], bins=bins)
plt.show()

在这里插入图片描述
当我们仅在年龄列上尝试特定查询时,我们会看到同样的准确性损失:

real_answer = range_query(adult, 'Age', 20, 30)
syn_answer = range_query(syn_df, 'Age', 20, 30)
print('Percent error using synthetic data:', pct_error(real_answer, syn_answer))

Percent error using synthetic data: 0.2110752421157189

总结

1、数据集的综合表示形式允许回答有关原始数据的查询

2、合成表示的一个常见示例是直方图,可以通过在其计数中添加噪声来使其差分隐私

3、直方图表示可用于生成与原始数据形状相同的合成数据,方法是将其计数视为概率:将计数归一化为 1,然后使用相应的归一化计数作为概率从直方图条柱中采样

4、归一化直方图是单向边际分布的表示形式,它孤立地捕获单个列中的信息

5、单向边际不捕获列之间的相关性

6、要生成多个列,我们可以使用多个单向边际,或者我们可以构造一个 n 位边际的表示形式,其中 n>1

7、随着n的增长,差分私有n路边际变得越来越嘈杂,因为较大的n 意味着生成的直方图的每个条柱的计数越小

8、因此,生成合成数据的挑战性权衡是:使用多个单向边际1-way会丢失列之间的相关性\使用n-way方式边际往往非常不准确

9、在许多情况下,生成既准确又能捕获列之间重要相关性的合成数据可能是不可能的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值