本地差分隐私与随机响应代码实现】差分隐私代码实现系列(十三)

https://blog.csdn.net/qq_41691212/article/details/122895319?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171158702116800215059302%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=171158702116800215059302&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-122895319-null-null.142^v100^pc_search_result_base1&utm_term=%E6%9C%AC%E5%9C%B0%E5%B7%AE%E5%88%86%E9%9A%90%E7%A7%81%20%E9%9A%8F%E6%9C%BA%E5%93%8D%E5%BA%94&spm=1018.2226.3001.4187

 

差分隐私代码实现系列(十三)
写在前面的话
回顾
本地差分隐私
随机响应
一元编码
总结
写在前面的话
书上学来终觉浅,绝知此事要躬行。

回顾
1、梯度下降是一种通过根据损失的梯度更新模型来使损失变小的方法。梯度就像一个多维导数:对于具有多维输入的函数(如上面的损失函数),梯度体现函数的输出相对于输入的每个维度的变化速度。如果梯度在特定维度中为正,则意味着如果我们增加该维度的模型权重,则该函数的值将增加;我们希望损失减少,因此我们应该通过朝着梯度的反方向来修改我们的模型,即做与梯度相反的事情。由于我们沿梯度相反的方向移动模型,因此这称为梯度下降。

2、一般来说,更多的训练迭代可以提高准确性,但更多的迭代需要更多的计算时间。大多数用于使大规模深度学习变得实用的"技巧"实际上都是为了加快梯度下降的每次迭代,以便在相同的时间内执行更多的迭代。

3、我们的目的是使得最终模型不会显示有关单个训练示例的任何信息。算法中唯一使用训练数据的部分是梯度计算。使算法具有差分隐私的一种方法是在每次迭代时在更新模型之前向梯度本身添加噪声。这种方法通常称为噪声梯度下降,因为我们直接将噪声添加到梯度中。

4、这里有两个主要挑战。首先,梯度是平均查询的结果,它是每个示例的许多梯度的平均值。正如我们之前所看到的,最好将此类查询拆分为总和查询和计数查询。这并不难做到,我们可以计算每个示例梯度的总和,而不是它们的平均值,并在以后除以噪声计数。其次,我们需要绑定每个示例梯度的灵敏度。有两种基本方法:我们可以分析梯度函数本身(就像我们在之前的查询中所做的那样)来确定其最坏情况下的全局灵敏度,或者我们可以通过剪裁梯度函数的输出来强制执行灵敏度(就像我们在样本和聚合中所做的那样)。

5、渐变剪裁算法的每次迭代都满足( ϵ , δ ) (\epsilon, \delta)(ϵ,δ)-差分隐私,我们执行一个额外的查询来确定满足ϵ \epsilonϵ-差分隐私的噪声计数。如果我们执行k kk迭代,则通过顺序组合,算法满足( k ϵ + ϵ , k δ ) (k\epsilon + \epsilon, k\delta)(kϵ+ϵ,kδ)-差分隐私。我们还可以使用高级组合来分析总隐私成本;更好的是,我们可以将算法转换为 Rényi 差分隐私或零集中差分隐私,并获得隐私成本的严格限制。

6、我们之前的方法非常通用,因为它不对梯度的行为做出任何假设。然而,有时我们确实对梯度的行为有所了解。特别是,一大类有用的梯度函数(包括我们在这里使用的逻辑损失的梯度)是利普希茨连续,这意味着它们具有有界的全局灵敏度。

7、裁剪训练示例而不是梯度有两个优点。首先,估计训练数据的比例(从而选择一个好的裁剪参数)通常比估计训练期间将要计算的梯度的尺度更容易。其次,它在计算上更有效:我们可以裁剪一次训练示例,并在每次训练模型时重用裁剪的训练数据。使用渐变剪切,我们需要在训练期间修剪每个梯度。此外,我们不再被迫计算每个示例的梯度,以便我们可以裁剪它们,相反,我们可以一次计算所有梯度,这可以非常有效地完成(这是机器学习中常用的技巧,但我们不会在这里讨论它)。

8、可以合理地预期,ϵ \epsilonϵ的值越小,模型的精度就越低(因为这是我们迄今为止看到的每个差分隐私算法的趋势)。这是事实,但也有一个稍微更微妙的权衡,这是由于我们在执行算法的多次迭代时需要考虑的组成:更多的迭代意味着更大的隐私成本。在标准梯度下降算法中,迭代次数越多,通常会产生更好的模型。在我们的差分隐私版本中,更多的迭代可能会使模型变得更糟,因为我们必须为每次迭代使用较小的ϵ \epsilonϵ,因此噪声的规模会上升。在差分隐私机器学习中,在使用的迭代次数和添加的噪声规模之间取得适当的平衡是很重要的(有时是非常具有挑战性的)。

本地差分隐私
到目前为止,前面所提到的模型只考虑了差分隐私的中心模型(我更喜欢称其为中心差分隐私~),其中敏感数据被集中收集在单个数据集中。

在此设置中,我们假设分析师是恶意的,但有一个受信任的数据管理者持有数据集并正确执行分析师指定的差分隐私机制。

此设置通常不现实。在许多情况下,数据管理者和分析师是相同的,实际上没有受信任的第三方来保存数据和执行机制。

事实上,收集最敏感数据的组织往往正是我们不信任的组织,这样的组织当然不能充当受信任的数据管理者。

差分隐私的中心模型的替代方案是差分隐私的本地模型,其中数据在离开数据主体的控制之前变得满足差分隐私。

例如,你可能会在将设备上的数据发送给数据管理机构之前,先向数据添加噪音。在本地模型中,数据管理者不需要信任,因为它们收集的数据已经满足了差分隐私。

因此,与中心模型相比,本地模型具有一个巨大的优势:数据主体除了他们自己之外,不需要信任其他人。

这一优势使其在现实世界的部署中很受欢迎,包括谷歌和苹果的部署。

不幸的是,本地模型也有一个明显的缺点:在中心差分隐私下,对于与相同查询相同的隐私成本,本地模型中查询结果的准确性通常要低几个数量级。

这种准确性的巨大损失意味着只有少数查询类型适用于本地差分隐私,即使对于这些查询类型,也需要大量的参与者。

在这篇博客中中,我们将看到两种用于本地差分隐私的机制。第一种称为(randomized response)随机响应,第二种称为(unary encoding)一元编码。

随机响应
1965年S. L. Warner在论文中提到的随机响应是一种局部差分隐私机制。

当时,该技术旨在改善对敏感问题的调查回复中的偏见,并且最初并未被提议作为差分隐私的机制。在开发差分隐私之后,统计学家意识到这种现有技术已经满足了定义。

Dwork和Roth提出了随机响应的变体,其中数据主体回答"是"或"否"问题,如下所示:

1、掷硬币~

2、如果硬币是正面(人头),请如实回答问题。

3、如果硬币是反面,请掷出另一枚硬币。

4、如果第二枚硬币是正面的,回答"是",否则回答"否"。

该算法中的随机化来自两次硬币翻转。与所有其他差分隐私算法一样,这种随机化对真实答案产生了不确定性,而真实答案是隐私的来源。

事实证明,这种随机响应算法满足ϵ \epsilonϵ-差分隐私的ϵ = log ⁡ ( 3 ) = 1.09 \epsilon = \log(3) = 1.09ϵ=log(3)=1.09。

让我们为一个简单的"是"或"否"问题实现算法:"你的职业是’销售’吗?我们可以在Python中使用np.random.randint(0, 2),结果为 0 或 1。

%matplotlib inline
import matplotlib.pyplot as plt
plt.style.use('seaborn-whitegrid')
import pandas as pd
import numpy as np

adult = pd.read_csv("adult_with_pii.csv")
def laplace_mech(v, sensitivity, epsilon):
return v + np.random.laplace(loc=0, scale=sensitivity / epsilon)
def pct_error(orig, priv):
return np.abs(orig - priv)/orig * 100.0
def rand_resp_sales(response):
truthful_response = response == 'Sales'

# 第一次抛硬币
if np.random.randint(0, 2) == 0:
# 如果抛正面回答正确答案
return truthful_response
else:
# 第二次抛硬币
return np.random.randint(0, 2) == 0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
让我们让200名从事销售工作的人使用随机响应来回答,并查看结果。

pd.Series([rand_resp_sales('Sales') for i in range(200)]).value_counts()
1

我们看到的是,我们得到"是"和"否",但"是"比"否"更重要。

这个输出展示了我们已经看到的差分隐私算法的两个特征:它包括不确定性,这保护了隐私,但也显示了足够的信号,使我们能够推断出关于人相关的东西。

让我们在一些实际数据上尝试同样的事情。我们将获取我们一直在使用的美国人口普查数据集中的所有职业,并为每个职业对"您的职业是’销售’吗?"问题的回答进行编码。

在实际部署的系统中,我们根本不会集中收集此数据集。相反,每个响应者都将在本地运行rand_resp_sales,并将其随机响应提交给数据管理者。对于实验,我们将在现有数据集上运行rand_resp_sales。

responses = [rand_resp_sales(r) for r in adult['Occupation']]
pd.Series(responses).value_counts()
1
2

这一次,我们得到的"否"比"是"多得多。经过一番思考,这很有道理,因为数据集中的大多数参与者都不在销售中。

现在的关键问题是:我们如何根据这些反应来估计数据集中销售人员的准确的数量?明显目前得到的"是"的数量对于销售人员的数量来说不是一个很好的估计:

len(adult[adult['Occupation'] == 'Sales'])
1

这并不奇怪,因为许多"是"来自算法的随机硬币翻转。

为了估计销售人员的真实数量,我们需要分析随机响应算法中的随机性,并估计有多少"是"响应来自实际销售人员,以及有多少是随机掷硬币产生的假的“是”。我们知道:

1、每个响应者随机响应的概率是1 2 \frac{1}{2}
2
1

2、每个随机的响应都是"是"的概率是1 2 \frac{1}{2}
2
1

因此,响应者随机回答"是"的概率(而不是因为他们是销售人员)是1 2 ⋅ 1 2 = 1 4 \frac{1}{2} \cdot \frac{1}{2} = \frac{1}{4}
2
1


2
1

=
4
1

。这意味着我们可以预期我们总回复的四分之一是假的"是"。

responses = [rand_resp_sales(r) for r in adult['Occupation']]

# we expect 1/4 of the responses to be "yes" based entirely on the coin flip
# these are "fake" yesses
fake_yesses = len(responses)/4

# the total number of yesses recorded
num_yesses = np.sum([1 if r else 0 for r in responses])

# the number of "real" yesses is the total number of yesses minus the fake yesses
true_yesses = num_yesses - fake_yesses
1
2
3
4
5
6
7
8
9
10
11
我们需要考虑的另一个因素是,一半的受访者是随机回答的,但一些随机的受访者实际上可能是销售人员。他们中有多少人是销售人员?我们没有这方面的数据,因为他们是随机回答的!

但是,由于我们将受访者随机分为"真相"和"随机"组(通过第一次掷硬币),我们可以希望两组中的销售人员数量大致相同。因此,如果我们能够估计"真相"组中的销售人员数量,则可以将此数字加倍,以获得销售人员总数。

rr_result = true_yesses*2
rr_result
1
2

这与销售人员的真实人数有多接近?让我们来比较一下!

true_result = np.sum(adult['Occupation'] == 'Sales')
true_result
1
2


pct_error(true_result, rr_result)
1

使用这种方法,以及相当大的计数(例如,在这种情况下超过3000),我们通常会得到"可接受"的错误率低于5%。如果你的目标是确定最受欢迎的职业,则此方法可能有效。但是,当计数较小时,误差将很快变大。

此外,随机响应比中心模型中的拉普拉斯机制差几个数量级。让我们比较一下此示例的两者:

pct_error(true_result, laplace_mech(true_result, 1, 1))
1

在这里,我们得到的误差约为0.01%,即使我们对中心模型的ϵ \epsilonϵ值略低于我们用于随机响应的ϵ \epsilonϵ值。

本地模型有更好的算法,但是在提交数据之前必须添加噪声的固有局限性意味着本地模型算法的准确性始终比最佳中心模型算法差。

一元编码
随机响应允许我们提出具有本地差异隐私的是/否问题。如果我们想构建直方图怎么办?

已经提出了许多不同的算法,用于在差分隐私的本地模型中解决这个问题。

Wang等人在2017年的一篇论文对一些最佳方法进行了很好的总结。

在这里,我将研究其中最简单的,称为一元编码。这种方法是Google的RAPPOR系统的基础(经过一些修改,使其随着时间的推移更好地适用于大型域和多个响应)。

第一步是定义响应的域,即我们关心的直方图条柱的标签。

对于我们的示例,我们想知道有多少参与者与每个职业相关联,因此我们的域是职业集。

domain = adult['Occupation'].dropna().unique()
domain
1
2

我们将定义三个函数,它们共同实现一元编码机制:

1、encode,对响应进行编码

2、perturb,这会扰动编码的响应

3、aggregate,从扰动响应中重建最终结果

这种技术的名称来自所使用的编码方法:对于一个k kk大小域,每个响应都被编码为k kk位的长度向量,除了对应于响应方的职业的位置之外,所有位置都为0。在机器学习中,这种表示被称为"one-hot encoding"。

例如,"Sales"是域的第 6 个元素,因此"Sales"职业使用第 6 个元素为 1 的向量进行编码。

def encode(response):
return [1 if d == response else 0 for d in domain]

encode('Sales')
1
2
3
4

下一步是perturb ,它翻转响应向量中的位以确保差分隐私。位被翻转的概率基于两个参数p pp和q qq,它们共同决定了隐私参数ϵ \epsilonϵ(基于我们稍后将看到的公式)。
P r [ B ′ [ i ] = 1 ] = { p        if    B [ i ] = 1 q        if    B [ i ] = 0 \mathsf{Pr}[B'[i] = 1] = \left\{
pifB[i]=1qifB[i]=0

if

[

]
=
1

if

[

]
=
0
\right.
Pr[B

[i]=1]={
pifB[i]=1
qifB[i]=0

def perturb(encoded_response):
return [perturb_bit(b) for b in encoded_response]

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

perturb(encode('Sales'))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

根据p pp和q qq的值,我们可以计算出隐私参数的值ϵ \epsilonϵ。对于p = . 75 p=.75p=.75和q = . 25 q=.25q=.25,我们将看到略大于 2 的一个ϵ \epsilonϵ。
ϵ = log ⁡ ( p ( 1 − q ) ( 1 − p ) q ) \epsilon = \log{\Big(\frac{p (1-q)}{(1-p) q}\Big)}
ϵ=log(
(1−p)q
p(1−q)

)

def unary_epsilon(p, q):
return np.log((p*(1-q)) / ((1-p)*q))

unary_epsilon(.75, .25)
1
2
3
4

最后一部分是聚合。如果我们没有做任何扰动,那么我们可以简单地获取响应向量集并逐个添加它们,以获得域中每个元素的计数:

counts = np.sum([encode(r) for r in adult['Occupation']], axis=0)
list(zip(domain, counts))
1
2

但正如我们在随机响应中看到的那样,由翻转位引起的"假"响应导致结果难以解释。如果我们对扰动响应执行相同的过程,则计数都是错误的:

counts = np.sum([perturb(encode(r)) for r in adult['Occupation']], axis=0)
list(zip(domain, counts))
1
2

一元编码算法的聚合步骤考虑了每个类别中"假"响应的数量,这是p pp和q qq的函数,以及响应的数量n nn:
A [ i ] = ∑ j B j ′ [ i ] − n q p − q A[i] = \frac{\sum_j B'_j[i] - n q}{p - q}
A[i]=
p−q

j

B
j


[i]−nq

def aggregate(responses):
p = .75
q = .25

sums = np.sum(responses, axis=0)
n = len(responses)

return [(v - n*q) / (p-q) for v in sums]

responses = [perturb(encode(r)) for r in adult['Occupation']]
counts = aggregate(responses)
list(zip(domain, counts))
1
2
3
4
5
6
7
8
9
10
11
12

正如我们在随机响应中看到的那样,这些结果足够准确,可以获得域元素(至少是最受欢迎的域元素)的粗略排序,但比我们在差分隐私中心模型中使用拉普拉斯机制获得的精度要低几个数量级。

已经提出了用于在本地模型中执行直方图查询的其他方法,包括前面论文中详细介绍的一些方法。这些可以在一定程度上提高准确性,但是在局部模型中必须确保每个样本的差分隐私的基本限制意味着,即使是最复杂的技术也无法与我们在中心模型中看到的机制的准确性相匹配。

总结
1、本地模型也有一个明显的缺点:在中心差分隐私下,对于与相同查询相同的隐私成本,本地模型中查询结果的准确性通常要低几个数量级。这种准确性的巨大损失意味着只有少数查询类型适用于本地差分隐私,即使对于这些查询类型,也需要大量的参与者。

2、当答案本身较小时,本地模型的误差会变大。在误差上,随机响应比中心模型中的拉普拉斯机制差几个数量级。即使本地模型有更好的算法,但是在提交数据之前必须添加噪声的固有局限性意味着本地模型算法的准确性始终比最佳中心模型算法差。

3、已经提出了用于在本地模型中执行直方图查询的其他方法,包括前面论文中详细介绍的一些方法。这些可以在一定程度上提高准确性,但是在局部模型中必须确保每个样本的差分隐私的基本限制意味着,即使是最复杂的技术也无法与我们在中心模型中看到的机制的准确性相匹配。
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/qq_41691212/article/details/122895319

posted on 2024-03-28 09:57  独上兰舟1  阅读(62)  评论(0编辑  收藏  举报