Local Differential Privacy本地化差分隐私
1.学习目标
- 定义差异隐私的局部模型,并与中心模型进行对比
- 定义并实现随机响应和一元编码机制
- 描述这些机制的准确性含义和本地模型的挑战
到目前为止,我们只考虑了差异隐私的中心模型,其中敏感数据集中收集在单个数据集中。在这种情况下,我们假设分析员是恶意的,但有一个可信的数据管理员,他持有数据集并正确地执行分析员指定的差分隐私机制。
这种设置通常不现实。在许多情况下,数据管理员和分析员是相同的,实际上没有可信的第三方来保存数据和执行机制。事实上,收集最敏感数据的组织往往恰恰是我们不信任的组织;这样的组织当然不能充当可信的数据管理者。
区别于中心化差分隐私的就是本地化差分隐私,在这种模型中,数据在离开数据主体的控制之前被区别地私有化
。
例如,你可能会在将设备上的数据发送给数据管理机构之前,先向数据添加噪音。在本地模型中,数据管理者不需要信任,因为它们收集的数据已经满足了差分隐私。
因此,本地模型比中心模型有一个巨大的优势:数据主体不需要信任任何人,只需要信任自己。这一优势使其在现实世界的部署中广受欢迎,包括谷歌和苹果的部署。
不幸的是,本地模型也有一个明显的缺点:在中心差分隐私下,对于与相同查询相同的隐私成本,本地模型中查询结果的准确性通常要低几个数量级。
这种准确性的巨大损失意味着只有少数查询类型适合本地差异隐私,即使对于这些类型,也需要大量参与者。
在本节中,我们将看到两种用于本地差异隐私的机制。第一种称为随机响应randomized response,第二种称为一元编码unary encoding。
2.randomized response随机响应
1965年S. L. Warner在论文中提到的随机响应是一种局部差分隐私机制。当时,这项技术旨在改善对敏感问题的调查回答中的偏差,它最初并不是作为一种差异隐私机制提出的(再过40年也不会被发明)。
在差异隐私被开发出来之后,统计学家意识到这种现有技术已经满足了定义。Dwork和Roth提出了一种随机反应的变体,其中数据受试者回答“是”或“否”问题如下:
1. 掷硬币
2. 如果硬币是正面(人头),请如实回答问题。
3. 如果硬币是反面,请掷出另一枚硬币。
4. 如果第二枚硬币是正面的,回答"是",否则回答"否"。
该算法中的随机化来自两次硬币翻转。与所有其他不同的私有算法一样,这种随机化产生了关于真实答案的不确定性,这是隐私的来源。
事实证明,这种随机响应算法满足ϵ-差分隐私的
ϵ
=
log
(
3
)
=
1.09
\epsilon=\log (3)=1.09
ϵ=log(3)=1.09
让我们为一个简单的"是"或"否"问题实现算法:"你的职业是’销售’吗?我们可以在Python中使用np.random.randint(0, 2)
,结果为 0 或 1。
def rand_resp_sales(response):
truthful_response = response == 'Sales'
if np.random.randint(0, 2) == 0:
# answer truthfully
return truthful_response
else:
# answer randomly (second coin flip)
return np.random.randint(0, 2) == 0
让我们让200名从事销售工作的人使用随机回答进行回答,并查看结果。
print(
pd.Series([rand_resp_sales('Sales') for i in range(200)]).value_counts()
)
True 151
False 49
dtype: int64
Process finished with exit code 0
我们看到的是,我们得到"是"和"否",但"是"比"否"更重要。
这个输出展示了我们已经看到的差分隐私算法的两个特征:它包括不确定性,这保护了隐私,但也显示了足够的信号,使我们能够推断出关于人相关的东西。
让我们在一些实际数据上尝试同样的事情。我们将获取我们一直在使用的美国人口普查数据集中的所有职业,并为每个职业对"您的职业是’销售’吗?"问题的回答进行编码。
在实际部署的系统中,我们根本不会集中收集这个数据集-相反,每个响应者将在本地运行rand_resp_sales
,并将其随机响应提交给数据管理员。对于我们的实验,我们将在现有数据集上运行rand_resp_sales
。
responses = [rand_resp_sales(r) for r in adult['Occupation']]
print(pd.Series(responses).value_counts())
False 22490
True 10071
dtype: int64
Process finished with exit code 0
这一次,我们得到的"否"比"是"多得多。经过一番思考,这很有道理,因为数据集中的大多数参与者都不在销售中。
现在的关键问题是:我们如何根据这些反应来估计数据集中销售人员的准确的数量?明显目前得到的"是"的数量对于销售人员的数量来说不是一个很好的估计:"是"的数量太少了。
print(len(adult[adult['Occupation'] == 'Sales']))
3650
这并不奇怪,因为许多"是"来自算法的随机硬币翻转。
为了估计销售人员的真实数量,我们需要分析随机响应算法中的随机性,并估计有多少"是"响应来自实际销售人员,以及有多少是随机掷硬币产生的假的“是”。我们知道:
**1. 每个响应者随机响应的概率是
1
2
\frac{1}{2}
21
2. 每个随机的响应都是"是"的概率是
1
2
\frac{1}{2}
21
因此,响应者随机回答"是"的概率(而不是因为他们是销售人员)是 1 2 ⋅ 1 2 = 1 4 \frac{1}{2} \cdot \frac{1}{2}=\frac{1}{4} 21⋅21=41。这意味着我们可以预期我们总回复的四分之一是假的"是"。**
我们需要考虑的另一个因素是,一半的受访者随机回答,但一些随机响应者实际上可能是销售人员。他们中有多少人是销售人员?我们没有这方面的数据,因为他们是随机回答的!
但是,由于我们将回答者随机分为“真实”和“随机”两组(通过第一次投币),我们可以希望两组中的销售人员数量大致相同。因此,如果我们可以估计“真相”小组中的销售人员数量,我们可以将这个数字加倍,得到销售人员的总数。
responses = [rand_resp_sales(r) for r in adult['Occupation']]
fake_yesses = len(responses) / 4
num_yesses = np.sum([1 if r else 0 for r in responses])
true_yesses = num_yesses - fake_yesses
rr_result = true_yesses * 2
true_result = np.sum(adult['Occupation'] == 'Sales')
print('rr_result: ', rr_result, ' true_result: ', true_result)
print('误差为:', pct_error(true_result, rr_result))
rr_result: 3419.5 true_result: 3650
误差为: 6.315068493150684
Process finished with exit code 0
使用这种方法和相当大的计数(例如,在本例中超过3000),我们通常会得到“可接受”的错误-大约低于5%。
如果你的目标是确定最受欢迎的职业,那么这种方法很可能奏效。然而,当计数较小时,误差会很快变大。
此外,随机响应比中心模型中的拉普拉斯机制差几个数量级。让我们比较一下此示例的两者:
print('加入laplace的误差是:', pct_error(true_result, laplace_mech(true_result, 1, 1)))
加入laplace的误差是: 0.01137909360069449
在这里,我们得到的误差约为0.01%,即使我们对中心模型的ϵ 值略低于我们用于随机响应的ϵ 值。
给相同的epsilon值,本地化达不到中心化的精度
。
本地模型有更好的算法,但是在提交数据之前必须添加噪声的固有局限性意味着本地模型算法的准确性始终比最佳中心模型算法差
。
3.Unary Encoding一元编码
随机化的回答允许我们提出一个有局部差异隐私的是/否问题。如果我们想建立直方图呢?
已经提出了许多不同的算法来解决差异隐私的局部模型中的这个问题。
Wang等人在2017年的一篇论文对一些最佳方法进行了很好的总结。
在这里,我将研究其中最简单的,称为一元编码。这种方法是Google的RAPPOR系统的基础(经过一些修改,使其随着时间的推移更好地适用于大型域和多个响应)。
第一步是定义响应的域——我们关心的直方图箱的标签。
例如,我们想知道有多少参与者与每个职业相关,所以我们的领域是职业集。
# dropna()如果数据集一行有一个元素为NAN 就把整行删除
domain = adult['Occupation'].dropna().unique()
print(domain)
['Adm-clerical' 'Exec-managerial' 'Handlers-cleaners' 'Prof-specialty'
'Other-service' 'Sales' 'Craft-repair' 'Transport-moving'
'Farming-fishing' 'Machine-op-inspct' 'Tech-support' 'Protective-serv'
'Armed-Forces' 'Priv-house-serv']
Process finished with exit code 0
我们将定义三个函数,它们共同实现一元编码机制:
1. encode
,对响应进行编码
2. perturb
,这会扰动编码的响应
3. aggregate
,从扰动响应中重建最终结果
这种技术的名称来自于所使用的编码方法:对于一个k大小域, 每个响应都编码为一个长度为k的向量。 除了对应于响应方的占用的位置之外,所有位置都为0。
在机器学习中,这种表示被称为"one-hot encoding
"。
1. encode
,对响应进行编码
例如,"Sales"是域的第 6 个元素,因此"Sales"职业使用第 6 个元素为 1 的向量进行编码。
def encode(response):
return [1 if d == response else 0 for d in domain]
print(encode('Sales'))
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]
2. perturb
,这会扰动编码的响应
下一步是perturb,它翻转响应向量中的位,以确保差异隐私。
比特被翻转的概率基于两个参数𝑝 和𝑞
,它们一起确定隐私参数𝜖 (基于我们稍后将看到的公式)。
Pr
[
B
′
[
i
]
=
1
]
=
{
p
if
B
[
i
]
=
1
q
if
B
[
i
]
=
0
\operatorname{Pr}\left[\boldsymbol{B}^{\prime}[i]=1\right]=\left\{\begin{array}{ll} p & \text { if } B[i]=1 \\ q & \text { if } B[i]=0 \end{array}\right.
Pr[B′[i]=1]={pq if B[i]=1 if B[i]=0
def encode(response):
return [1 if d == response else 0 for d in domain]
# 大概率不变.75 小概率变.25
def perturb_bit(bit):
p = .75
q = .25
sample = np.random.random()
if bit == 1:
if sample <= p:
return 1
else:
return 0
elif bit == 0:
if sample <= q:
return 1
else:
return 0
def perturb(encoded_response):
return [perturb_bit(b) for b in encoded_response]
print('perturb(encode(Sales)):', perturb(encode('Sales')))
perturb(encode(Sales)): [1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0]
基于的值𝑝 和𝑞, 我们可以计算隐私参数的值𝜖. 对于𝑝=.75和𝑞=.25,我们将看到𝜖 略大于2。
ϵ
=
log
(
p
(
1
−
q
)
(
1
−
p
)
q
)
\epsilon=\log \left(\frac{p(1-q)}{(1-p) q}\right)
ϵ=log((1−p)qp(1−q))
def unary_epsilon(p, q):
return np.log((p*(1-q)) / ((1-p)*q))
print('unary_epsilon(.75, .25): ', unary_epsilon(.75, .25))
unary_epsilon(.75, .25): 2.1972245773362196
3. aggregate
,从扰动响应中重建最终结果
最后一部分是聚合。如果我们没有做任何扰动,那么我们可以简单地获取一组响应向量,并将它们逐元素相加,以获得域中每个元素的计数:
counts = np.sum([encode(r) for r in adult['Occupation']], axis=0)
print('counts', counts)
print('list(zip(domain, counts))', list(zip(domain, counts)))
counts [3770 4066 1370 4140 3295 3650 4099 1597 994 2002 928 649 9 149]
list(zip(domain, counts)) [('Adm-clerical', 3770), ('Exec-managerial', 4066), ('Handlers-cleaners', 1370), ('Prof-specialty', 4140), ('Other-service', 3295), ('Sales', 3650), ('Craft-repair', 4099), ('Transport-moving', 1597), ('Farming-fishing', 994), ('Machine-op-inspct', 2002), ('Tech-support', 928), ('Protective-serv', 649), ('Armed-Forces', 9), ('Priv-house-serv', 149)]
但正如我们在随机响应中看到的,由翻转比特引起的“假”响应导致结果难以解释。如果我们对受干扰的响应执行相同的程序,则计数都是错误的:这里都是错误的,都变大了很多。
counts = np.sum([perturb(encode(r)) for r in adult['Occupation']], axis=0)
print('perturb翻转过后counts', counts)
print('perturb翻转过后list(zip(domain, counts))', list(zip(domain, counts)))
perturb翻转过后counts [10010 10099 8839 10213 9901 9827 10174 8884 8675 9137 8667 8331
8237 8119]
perturb翻转过后list(zip(domain, counts)) [('Adm-clerical', 10010), ('Exec-managerial', 10099), ('Handlers-cleaners', 8839), ('Prof-specialty', 10213), ('Other-service', 9901), ('Sales', 9827), ('Craft-repair', 10174), ('Transport-moving', 8884), ('Farming-fishing', 8675), ('Machine-op-inspct', 9137), ('Tech-support', 8667), ('Protective-serv', 8331), ('Armed-Forces', 8237), ('Priv-house-serv', 8119)]
一元编码算法的聚合步骤考虑了每个类别中“假”响应的数量,这是两者的函数𝑝 和𝑞, 以及响应的数量𝑛:
A
[
i
]
=
∑
j
B
j
′
[
i
]
−
n
q
p
−
q
A[i]=\frac{\sum_{j} B_{j}^{\prime}[i]-n q}{p-q}
A[i]=p−q∑jBj′[i]−nq
responses = [perturb(encode(r)) for r in adult['Occupation']]
counts = aggregate(responses)
print('aggregate聚合以后perturb翻转过后counts', counts)
print('aggregate聚合以后perturb翻转过后list(zip(domain, counts))', list(zip(domain, counts)))
aggregate聚合以后perturb翻转过后counts [4129.5, 3721.5, 1119.5, 4241.5, 3407.5, 3619.5, 3903.5, 1639.5, 919.5, 1843.5, 1087.5, 455.5, -106.5, 109.5]
aggregate聚合以后perturb翻转过后list(zip(domain, counts)) [('Adm-clerical', 4129.5), ('Exec-managerial', 3721.5), ('Handlers-cleaners', 1119.5), ('Prof-specialty', 4241.5), ('Other-service', 3407.5), ('Sales', 3619.5), ('Craft-repair', 3903.5), ('Transport-moving', 1639.5), ('Farming-fishing', 919.5), ('Machine-op-inspct', 1843.5), ('Tech-support', 1087.5), ('Protective-serv', 455.5), ('Armed-Forces', -106.5), ('Priv-house-serv', 109.5)]
正如我们在随机响应中看到的那样,这些结果足够精确,可以获得域元素(the domain elements)的粗略排序(至少是最流行的),但比我们在差异隐私的中心模型中使用拉普拉斯机制获得的精度要低几个数量级
。
已经提出了用于在本地模型中执行直方图查询的其他方法,包括前面论文中详细介绍的一些方法。这些可以在一定程度上提高准确性,但是在本地模型中必须确保每个样本的差分隐私的基本限制意味着,即使是最复杂的技术也无法与我们在中心模型中看到的机制的准确性相匹配
。
结论
- 本地模型也有一个明显的缺点:在中心差分隐私下,对于与相同查询相同的隐私成本,本地模型中查询结果的准确性通常要低几个数量级。这种准确性的巨大损失意味着只有少数查询类型适用于本地差分隐私,即使对于这些查询类型,也需要大量的参与者。
- 当答案本身较小时,本地模型的误差会变大。在误差上,随机响应比中心模型中的拉普拉斯机制差几个数量级。即使本地模型有更好的算法,但是在
提交数据之前必须添加噪声的固有局限性
意味着本地模型算法的准确性始终比最佳中心模型算法差。 - 已经提出了用于在本地模型中执行直方图查询的其他方法,包括前面论文中详细介绍的一些方法。这些可以在一定程度上提高准确性,但是在局部模型中必须确保每个样本的差分隐私的基本限制意味着,即使是最复杂的技术也无法与我们在中心模型中看到的机制的准确性相匹配。