差分隐私中常见的处理方法-随机响应(一枚不公正的硬币)


参考自:

知乎:Dper

Github:DPer
如果感觉有用的话,希望去Github上star一下原作者!!!

0.事先声明

1. 内容基于https://github.com/forestneo/sunDP的开源项目而整理。
2. 内容出自个人理解,并不保证正确性。
3. 本文主要是针对上述开源项目中的算法进行学习和整理。

1.local_differential_privacy_library中的算法

函数名称功能
eps2p(epsilon, n=2)计算概率
discretization(value, lower=0, upper=1)离散化
random_response(bits: np.ndarray, p, q=None)随机响应
k_random_response(value, values, epsilon)k随机响应
unary_encoding(bits: np.ndarray, epsilon)UE
symmetric_unary_encoding(bits: np.ndarray, epsilon)SUE
optimized_unary_encoding(bits: np.ndarray, epsilon)OUE

eps2p(epsilon, n=2),计算概率

  • 此函数是用来计算概率的,这个概率是核心部分。用来实现后面随机响应({0,1}反转)。

  • 概率给出
    p = e ε e ε + 1 p=\frac{e^{\varepsilon}}{e^{\varepsilon}+1} p=eε+1eε

  • 说明:

    • 这是一个单调增函数,假定 ε ∈ [ 0 , + ∞ ) \varepsilon \in [0,+\infty) ε[0,+) ,那么函数的值域 p ∈ [ 1 2 , 1 ) p \in [\frac{1}{2},1) p[21,1)
    • 随着 ε \varepsilon ε的逐渐增大, p p p的概率也在逐渐增大,np.random.binomial(1, p, len(bit_array))函数上看,数据维持原样的概率增大,扰动数据越接近真实数据,保护的强度也在减小。这个和预期是一致的。( ε \varepsilon ε越大,扰动越小,保护强度越小。 ε \varepsilon ε越小,扰动越大(在 ε = 0 \varepsilon=0 ε=0时, p = 1 2 p=\frac{1}{2} p=21,此时数据都有可能发生反转),保护强度越高)
    • 虽然现在我还了解为什么要这样设置 p p p的概率,是从实验结果上来看,效果还是可以的。
    • 从函数设计上说,这就是一个sigmod函数。
  • 函数定义:

    # 这个是核心的概率
    # 下面的这个函数是单调增函数
    # 假定epsilon的值 >= 0,函数的值域为[0,1)
    def eps2p(epsilon, n=2):
        return np.e ** epsilon / (np.e ** epsilon + n - 1)
    

discretization(value, lower=0, upper=1),离散化

  • 这里原作者使用了传统的离散化处理

  • 计算概率:
    p [ v a l u e ] = v a l u e − l o w e r u p p e r − l o w e r p[value]=\frac{value-lower}{upper-lower} p[value]=upperlowervaluelower

  • 这个 p [ v a l u e ] p[value] p[value]的大小对于最终离散化后结果取lower还是取upper的影响是重要的。

  • 举例说明,假定 p [ v a l u e ] = 0.8 p[value]=0.8 p[value]=0.8,则说明分子比较接近分母(给定lower和upper的情况下,分母是定值),即 v a l u e − l o w e r value-lower valuelower越接近 u p p e r − l o w e r upper-lower upperlower,说明 v a l u e value value越接近 u p p p e r uppper uppper

  • 当然我们可以把这个计算出来的概率和 0.5 0.5 0.5做比较,只要它 p [ v a l u e ] p[value] p[value]大于等于0.5,就返回upper(1), p p p小于0.5,就返回lower(0)。这是一种很朴素的想法。

  • 但是原作者,为了离散化更随机,加入了一个和随机数比较的过程。假定生成一个随机数 r n d rnd rnd,当发生 r n d < p [ v a l u e ] rnd< p[value] rnd<p[value]的概率越大,说明 p [ v a l u e ] p[value] p[value]的值越大,value的值越接近upper(1),反之越接近lower(0)。

  • 函数定义:

    # 这里的离散化可不可以改变一下?
    def discretization(value, lower=0, upper=1):
        if value > upper or value < lower:
            raise Exception("the range of value is not valid in Function @Func: discretization")
    
        p = (value - lower) / (upper - lower)
        rnd = np.random.random()
        return upper if rnd < p else lower
    
  • 计算 p [ v a l u e ] p[value] p[value]的方法是可以自定义。(毕竟离散化的方法有很多)

  • 是否引入和随机数比较,这也是可以自定义的。

random_response(bit_array: np.ndarray, p, q=None),随机响应

  • 随机响应式很常见的机制。个人感觉,使用这种方式的好处在于很容易通过扰动后的数据进行反推,近似得出近似原数据的直方图统计(至少从实验上看,是可行的)。

  • 原作者在这里使用的基于二项式分布随机响应。从np.random.binomial()函数就可以看出。

  • 原作者在这里是以一个向量(具体对对应的就是数据集中的一个样本)做随机响应。

  • 说明:原作者在github上的代码和在知乎上关于这部分的阐述有表述上的差异。个人感觉github上中代码在这部解释较好。

  • 为什么采用随机扰动,后面再解释。

  • 函数定义:

    def random_response(bit_array: np.ndarray, p, q=None):
        """
        :param bit_array:
        :param p: probability of 1->1
        :param q: probability of 0->1
        update: 2020.03.06
        :return:
        """
        q = 1-p if q is None else q
        # 类型检查
        # 如果bit_array是一个标量的int,那么这个isinstance(bit_array, int)就为真,
        # 但是传入的一个一维的数组,这判断就为假,直接执行后面的return
        if isinstance(bit_array, int):
            probability = p if bit_array == 1 else q
            return np.random.binomial(n=1, p=probability)
        return np.where(bit_array == 1, np.random.binomial(1, p, len(bit_array)), np.random.binomial(1, q, len(bit_array)))
    
    
    • 虽然原作者这里使用是的二项分布的api,但是本质上是一个两点的{0,1}分布,一次只做一次抛硬币的实验,生成 l e n ( l e n ( b i t _ a r r a y ) ) len(len(bit\_array)) len(len(bit_array))的序列而已。
    • 原作者使用单次的二项分布。其他,如正太分布进行生成数据进行随机响应,是否可以?。(有这个想法,但是还么有测试)
    • 使用上述函数的前提是,传入的bit_array是np.ndarray类型。而不是变量。在原作者的使用中,是以一维向量传入。
    • np.where()函数的使用。(这个一开始没看懂,现在看懂了)

random_response_adjust(s: np.ndarray, data_len, prob):随机响应的反推

  • 这个函数的定义和解释,在原作者的知乎上有,但是在Github上没有。自己把它写完贴上了

  • 理论解释:

    • 假定数据集中共有 N N N条数据(每个数据非0即1),其中有 x x x个1,则有 N − x N-x Nx个0,通过随机响应处理之后,统计到 s s s个1(求和即可),那么估计原来 N N N条记录中应该有多个1? p p p为不反转的概率。

    • s = x ∗ p + ( N − x ) ∗ ( 1 − p ) s=x*p+(N-x)*(1-p) s=xp+(Nx)(1p)

    • x ~ = s + p ∗ N − N 2 ∗ p − 1 \widetilde{x}=\frac{s+p*N-N}{2*p -1} x =2p1s+pNN

  • 函数定义:

    def random_response_adjust(s: np.ndarray, data_len, prob):
        """
        对random response的结果进行校正
        :param s: 收到数据中1的个数,观察到的1的总数
        :param data_len: 总的数据个数,样本个数
        :param prob: eps2p()
        :return: 估计实际中1的个数
        """
        return (s + prob * data_len - data_len) / (2 * prob - 1)
    
    
  • 真正的随机响应机制:

    1. 基于之前的概率计算,这个对于隐私保护预算 ε \varepsilon ε的确定很重要
      p = e ε e ε + 1 ⇒ ε = ln ⁡ p 1 − p p=\frac{e^{\varepsilon}}{e^{\varepsilon}+1} \Rightarrow \varepsilon = \ln \frac{p}{1-p} p=eε+1eεε=ln1pp

    2. 举例说明:

      ​ 设对 n n n个用户(样本总数)的问答进行统计,得到患病人数的统计值。其中真实患病的比例记为 π \pi π

      ,假定回答“yes”的人数为 n 1 n_1 n1,回答"no"的人数记为 n 2 n_2 n2 p p p为非均匀硬币出现的正面概率。【规定,若抛出正面则必须诚实回答;若抛出反面,则可以随意回答。意思就是,正面说出自己的真实患病情况,反面的话,允许说假话,也可以说真话。这就是随机响应】

      有:
      P r [ x i = " y e s " ] = π ∗ p + ( 1 − π ) ∗ ( 1 − p ) P r [ x i = " n o " ] = ( 1 − π ) ∗ p + π ∗ ( 1 − p ) Pr[x_i="yes"]=\pi * p + (1 - \pi)*(1-p)\\ Pr[x_i="no"]=(1-\pi)*p + \pi * (1-p) Pr[xi="yes"]=πp+(1π)(1p)Pr[xi="no"]=(1π)p+π(1p)

      • 公式解释,一个用户的回答是”yes“,会有一种情况。一,自己真的患病,并且说了真话。二,自己没有患病,但是说了假话。同理,一个用户的回答是”no“,也是来自两个方面。【随机响应】

      为了得到无偏估计,采用极大似然的方法进行估计:
      L = ( π ∗ p + ( 1 − π ) ∗ ( 1 − p ) ) n 1 ∗ ( ( 1 − π ) ∗ p + π ∗ ( 1 − p ) ) n − n 1 L=(\pi * p + (1 - \pi)*(1-p))^{n_1}*((1-\pi)*p + \pi * (1-p))^{n-n_1} L=(πp+(1π)(1p))n1((1π)p+π(1p))nn1
      求导,求极值。得到 π \pi π的估计, π ~ \widetilde{\pi} π 。至于怎么求,基本操作,略。
      π ~ = p − 1 2 p − 1 + n 1 ( 2 p − 1 ) n \widetilde{\pi}=\frac{p-1}{2p-1}+\frac{n_1}{(2p-1)n} π =2p1p1+(2p1)nn1
      则,估计的患病人数 n ~ \widetilde{n} n 可表示为:
      n ~ = n ∗ π ~ = p − 1 2 p − 1 n + n 1 2 p − 1 \widetilde{n}=n* \widetilde{\pi}=\frac{p-1}{2p-1}n+\frac{n_1}{2p-1} n =nπ =2p1p1n+2p1n1

      • 上述公式的几点说明
        1. n n n是样本总数,这个是可以值得通过数据集统计得到的。 n 1 n_1 n1是数据集中回答"yes"的人数,这也是可以统计得到。 问题是i, p p p是如何等到的或者设定的?
        2. 一般的是把 p = e ε e ε + 1 p=\frac{e^\varepsilon}{e^\varepsilon +1} p=eε+1eε ε \varepsilon ε成为隐私预算。这个就是一开始提到的关键点。反之通过 p p p也可反推出 ε \varepsilon ε ε = ln ⁡ p 1 − p 。 \varepsilon = \ln \frac{p}{1-p} 。 ε=ln1ppp , ε ,\varepsilon ,ε是增长是同向的。
        3. 一般的为了能够获得真实的情况,希望用户总是说真话,那么 p p p的概率能够大一些(至少要大于0.5,因为公正的抛硬币都0.5了。)。说明 p p p的默认概率应该在( 1 2 \frac{1}{2} 21,1)之间。
      • 一般情况下,我们设 ε = ln ⁡ 3 \varepsilon=\ln3 ε=ln3,反过来就是设 p = 3 4 = 0.75 p=\frac{3}{4}=0.75 p=43=0.75。记住,这是一枚不公正的硬币。

k_random_response(value, values, epsilon):k随机响应

  • 计算概率p
  • 从原作者关于该函数的设计上看,在 l e n ( v a l u e s ) len(values) len(values)一定的情况下, ε \varepsilon ε越小,返回原来数据的可能性越小。在 ε \varepsilon ε一定的情况下, l e n ( v a l u e s ) len(values) len(values)越大,返回原来数据的可能性越小。
  • 结论: ε \varepsilon ε越小, l e n ( v a l u e s ) len(values) len(values)越大,返回原数据的可能性越小,返回values中非value中的数据的可能性就越大,保护的可能性就越大。
  • 函数定义:
# 返回value值,或返回values中的非value值
def k_random_response(value, values, epsilon):
    """
    the k-random response
    :param value: current value
    :param values: the possible value
    :param epsilon: privacy budget
    :return:
    """
    if not isinstance(values, list):
        raise Exception("The values should be list")
    if value not in values:
        raise Exception("Errors in k-random response")
    p = np.e ** epsilon / (np.e ** epsilon + len(values) - 1)
    if np.random.random() < p: # p是一个大于1/2概率的值
        return value
    values.remove(value) # 将value移除
    return values[np.random.randint(low=0, high=len(values))] # 返回values中任意一个非value的值

unary_encoding(bit_array: np.ndarray, epsilon),一元编码

  • 虽然原作者写了这个函数,但是在项目中明没有用。

  • 这个和下面的symmetric_unary_encoding是一样的

  • 函数定义:

    # 这个函数和下面的对称的symmetric_unary_encoding一元编码(反转)是一样的
    def unary_encoding(bit_array: np.ndarray, epsilon):
        """
        the unary encoding, the default UE is SUE
        update: 2020.02.25
        """
        if not isinstance(bit_array, np.ndarray):
            raise Exception("Type Err: ", type(bit_array))
        return symmetric_unary_encoding(bit_array, epsilon)
    

symmetric_unary_encoding(bit_array: np.ndarray, epsilon)

  • 虽然原作者写了这个函数,但是在项目中明没有用。

  • 这里的对称性symmetric,个人感觉体现在 p+q=1上

  • 这个函数的本质上自定义概率 p , q p,q p,q(见随机响应random_response)来进行随机响应。

  • 函数定义:

    # 规定 epsilon >= 0 恒成立
    # p+q=1
    # 当且仅当,epsilon=0时,p=q=1/2,此时隐私保护的强度最大(因为大家都有可能反转)
    # 一般情况下,p!=q 且 p > q
    # ==>也就是说,随着epsilon的逐渐增大,p越大于q,大家都尽可能的不反转(保持原样),数据就越真实,保护强度就越小,这和预期一致
    # 这里的对称性,个人感觉体现在 p+q=1上
    def symmetric_unary_encoding(bit_array: np.ndarray, epsilon):
        p = eps2p(epsilon / 2) / (eps2p(epsilon / 2) + 1)
        q = 1 / (eps2p(epsilon / 2) + 1)
        return random_response(bit_array, p, q)
    

optimized_unary_encoding(bit_array: np.ndarray, epsilon)

  • 虽然原作者写了这个函数,但是在项目中明没有用。

  • 这里的对称性symmetric,个人感觉体现在 p+q !=1(大部分情况下)上

  • 这个函数的本质上自定义概率 p , q p,q p,q(见随机响应random_response)来进行随机响应。

  • 函数定义:

    # p的概率1/2固定, 1->1或者1->0的概率是一样的。
    # epsilon的值越小,q的概率就越接近与1/2。即0->1的概率逐渐增大到1/2(结合下面的公式)。
    # 当且仅当 epsilon的值为0时,此时p=q=1/2,大家都是公平反转,保护强度最大,一般而言epsilon越小,越接近这个增强保护的趋势
    # 而且p+q !=1 (一般情况下,结合下面的公式)
    # 这里的优化,个人感觉体现在,p+q !=q(大多数情况下);且p恒等于1/2,q也有较小可能实现0->1反转
    # 优化点在与,无论epsilon怎样变化,都会提供数据保护(数据发生反转的概率总是较大,至少1反转的概率始终是1/2)
    # epsilon的逐渐增大,只会让0尽可能的不发生反转(1该以1/2概率反转还是反转);0尽可能的不反转,也是保证了部分数据没有被保护
    # 结论就是,epsilon逐渐增大,数据护的强度就会减弱,这和预期是一致的
    def optimized_unary_encoding(bit_array: np.ndarray, epsilon):
        p = 1 / 2
        q = 1 / (eps2p(epsilon) + 1)
        return random_response(bit_array, p, q)
    

2.项目中的实验部分,待整理!

  • 5
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值