差分隐私防护技术的介绍与实践(下)

admin 2022年5月31日17:53:02评论99 views字数 14885阅读49分37秒阅读模式
一、
序 

前面在《差分隐私介绍与实践》的上篇中系统的介绍了差分隐私。那么在本篇章将主要讲讲差分隐私在深度学习方面的实践。


二、
差分隐私保护实践


2.1   模型反转攻击

2015年,Matt Fredrikson 等学者提出了模型反转攻击[1]。该攻击方法主要利用机器学习模型的置信度来反推训练数据。作者通过实验证明,该攻击方法能够仅仅使用一个姓名就能够通过人脸预测模型还原训练时所使用的人脸图像。如图2-1所示,其中左边的图为模型反转攻击通过姓名还原的图,而右边则是模型训练时使用的图。通过对比不难发现,还原出来的图和训练时的用图具有很大的相似性。


差分隐私防护技术的介绍与实践(下)

2-1 利用模型反转攻击还原图与训练时图的对比

 

上述攻击案例中。模型反转攻击利用的学习模型的置信度还原训练数据,在一定程度上就是利用了统计数据库再识别技术。换句话说,模型反转攻击通过置信度(学习模型的结果)对训练数据中单条记录的不同的敏感度来猜训练数据的特点。这表明在机器学习领域应用差分隐私保护是必要的。


2.2   差分隐私应用到机器学习的基本思想

差分隐私的基本思想是通过添加随机噪声来降低结果对训练数据引起的差分的敏感度。所以,在机器学习领域,差分隐私保护要求在加入随机性后,攻击者很难去区分在学习模型的最后产生的结果哪些是来自随机噪声,哪些是来自真正的训练数据。也就是说,需要做到当改变训练集中的单条训练样本,模型最终训练出来的结果(参数)是保持大致相同的。如图2-2所示,两个模型在存在一条训练样本差分的情况下,仍然可以通过随机函数产生相同的结果,那攻击者就无法通过结果反推训练样本的信息。

 

差分隐私防护技术的介绍与实践(下)

2-2 差分隐私应用到机器学习基本思想


2.3   PATE

Google Brain 的研究团队,在2017年提出了PATE方案[2]PATE( Private Aggregation of Teacher Ensembles) 翻译为教师综合下的隐私聚合,它通过差分隐私保护技术实现了在不暴露原始数据的前提下训练公共数据。PATE 基于一个简单的想法:如果两个独立的分类器(机器学习模型),双方在没有共同训练样本的不同数据集上训练,最终如果对一个新的输入样本达成了相同的分类结果,那么就不会泄露单个训练样本的信息。但是如果产生了不同的结果,那可能会暴露与输入样本记录相像的单个训练样本的信息。例如:假设张三患有癌症,并且张三只将病历信息贡献其中一个分类器。现有一个与张三病历相似的输入样本给到这两个学习模型预测,如果训练过张三样本的分类器返回了癌症的结果,而另外一个样本没有。那么很有可能返回癌症结果的分类器因为学习过张三的病历信息,所以产生了癌症结果。这样就暴露了张三患癌症的隐私。


那么,PATE是如何基于上述想法进行隐私机器学习的呢?具体步骤如下,其中步骤a-c 如图2-3所示,步骤d-f 如图2-4所示。


a. 将隐私数据集划分为N个数据子集。注意:这个N个数据集之间是没有重叠的样本的(为何这里需要不重叠数据集呢?不妨假设所有的教师都使用同一个数据集训练,那么所有的教师输出的结果将会是一样的。去除其中一个或多个教师多结果不会造成影响,这跟仅仅使用一个教师模型是一样的效果了,差分隐私起不到作用,因为分类器的结果将变得单一,增加噪声无任何意义。
b. 针对这个N个子集,分别使用N个称为“教师模型” 学习模型进行训练。
c. 等所有教师模型训练完成后,这N个教师模型就相当于有N个预测模型了。

此时,教师模型是没有差分隐私保护的,单个教师模型产生的预测结果可能会单个记录的隐私信息的影响。因此需要对教师模型的分类结果进行隐私聚合处理以保护隐私。具体步骤如下:

d. 统计所有教师的结果,并对统计结果增加符合高斯分布的噪声。
e. 使用RNM 算法返回投票值最高的标签。这个投票值是含有噪声的。
f.  最终输出聚合结果。

差分隐私防护技术的介绍与实践(下)

2-3 教师模型训练过程


差分隐私防护技术的介绍与实践(下)

图 2-4 教师模型的隐私聚合过程

 

经过多个不同隐私数据训练的独立的教师模型的结果经过聚合以后,由于噪声的影响,其最终的输出结果将不再对单个训练样本的差分而敏感。但是这里面仍然存在一些受限的地方,主要有以下两个制约:


1. 每一次教师模型的预测,都将增大隐私预算

2. 教师模型的中间过程必须是保密的,一旦中间过程泄露,那么恶意攻击者将直接获取到单个教师模型的输出,那么差分隐私保护就失效了。


针对上述问题,PATE引入了学生模型。学生模型通过迁移教师模型的知识来完成训练。对于公开的数据但无标签的数据集,可以通过调用PATE中教师模型的隐私聚合查询API获得预测值,从而变成有标签的数据集,然后学生模型使用这些数据进行训练。


2.4   对PATE模型的隐私损失的分析

PySyft 是在Github 上开源的,用于在深度学习框架中保护训练集隐私的工具集。主要通过联邦学习、差分隐私保护和加密计算(比如多方安全计算和同态加密)等技术来保护隐私。本节将使用PySyft 中集成的PATE分析工具来估算在使用了PATE框架以后的隐私损失情况。PySyft pate工具中的perform_analysis 方法会给出两个值,分别是Data Dependent Epsilon  Data Independent EpsilonData Dependent Epsilon 意味着结合数据本身和当前差分隐私 ε 取值来看,隐私损失的情况,而Data Independent Epsilon 抛开数据本身,仅分析差分隐私 ε 取值所对应的隐私损失。


所以首先需要安装 PySyftPySyft的安装非常方便,直接使用pip install syft安装即可,这里建议安装0.2.9版本,太新的版本在结构上有所改动。为了方便演示以及后期读者的复现,我在此使用Google Colab 在线平台进行实验。具体步骤如下:


1. 首先进入到 Google Colab 平台,新建笔记本。

2. Google Colab 使用了jupyter notebook 那一套框架,所以可以在上面执行python代码,首先安装依赖,输入命令:!pip install syft==0.2.9 平台会给我们分配资源并且进行依赖安装,安装完成后需要点击重启notebook按钮。如下图2-5所示


差分隐私防护技术的介绍与实践(下)


差分隐私防护技术的介绍与实践(下)

2-5 环境安装


3. 导入所需要的依赖包,主要是pate分析工具和numpynumpy用于生成模拟数据),执行命令:import numpy as np from syft.frameworks.torch.dp import pate


4. 定义RNM方法,代码如下:

def cal_max(teacher_predsnum_labels):

indices = []

for i in range(teacher_preds.shape[1]):

label_counts = np.bincount(teacher_preds[:,i], minlength=num_labels)

max_label = np.argmax(label_counts)

indices.append(max_label)

return np.array(indices)

 

def noisy_max(teacher_predsprivacy_loss_lvn_labels): 

indices = []

for i in range(teacher_preds.shape[1]):

label_counts = np.bincount(teacher_preds[:,i], minlength=n_labels)

noisy_counts = label_counts + np.random.laplace(np.zeros(len(label_counts)), np.ones(len(label_counts))/privacy_loss_lv, len(label_counts))

indices.append(np.argmax(noisy_counts))

return np.array(indices)

 

5. 开始实验,整个实验的思路如下:实验会通过四个实例来加深对PATE的理解,前三个实例为了让教师模型的聚合结果尽可能的接近原始数据本身,所以使用了较大的隐私等级 ε,因为 ε 越大,隐私保护程度越低,数据越接近与原始数据。具体步骤如下:


实例1,假设教师模型数量为100个,测试样例1000个,所有的教师模型的输出都0。竟然所有的输出都为0,也就是说这里面不存在输出结果的差分,所以我们可以预计这里面是没有隐私信息泄露的。


使用numpy构造数据,并通过PySyft 的pate工具分析,执行下列代码:

num_teachers, num_examples, num_possible_answers = (100010001)

preds = np.zeros((num_teachers, num_examples, )).astype(int)

indices = cal_max(preds, num_possible_answers)

 

data_dep_eps, data_ind_eps = pate.perform_analysis(teacher_preds=preds, indices=indices, noise_eps=5, delta=1e-5, moments=70)

print("Data Independent Epsilon:", data_ind_eps)

print("Data Dependent Epsilon:", data_dep_eps)

 

最后得到的输出与我们预想的一样。如图2-6所示,其中Data Dependent Epsilon 的值非常低,而Data Independent Epsilon 比较高,这是因为所有教师模型的输出都为0,结果一样。也就是说无论怎么改变单个输入样本,结果都不会变。所以结合数据本身来看是没什么隐私泄露的,而由于我们的 ε 取值太高,所以抛开数据本身,是存在隐私泄露风险的。

差分隐私防护技术的介绍与实践(下)

2-6  实例1pate分析结果

 

实例2,假设教师模型100个,测试样例1000个,每一个教师模型的输出都是从0-999。具体如图2-7所示。和实例1不同的是,现在不同的输入样本会产生不同的值,但是由于所有教师模型对每一个测试样例输出的结果都是一样的,去除其中一个或多个教师模型,并不会影响最终的结果。所以也不会有隐私信息的泄露。

差分隐私防护技术的介绍与实践(下)

2-7 实例2的教师模型输出


使用numpy生成数据,执行pate分析,执行下列代码:

num_teachers, num_examples, num_possible_answers = (10010001000)

preds = np.zeros((num_teachers, num_examples, )).astype(int) + np.expand_dims(np.array(list(range(num_examples))), 0)

indices = cal_max(preds, num_possible_answers)

data_dep_eps, data_ind_eps = pate.perform_analysis(teacher_preds=preds, indices=indices, noise_eps=5, delta=1e-5, moments=70)

print("Data Independent Epsilon:", data_ind_eps)

print("Data Dependent Epsilon:", data_dep_eps)

 

差分隐私防护技术的介绍与实践(下)

2-8 实例2pate分析结果

 

最后得到的输出不出所料。如图2-8所示,其中Data Dependent Epsilon 的值非常低,说明结合数据来看,隐私损失很小。


实例3,假设教师模型100个,测试样例1000个,每一个教师模型的输出都是随机生成的,区间范围为0-99。具体如图2-9所示。和实例2不同都是,实例3中每一个教师模型对每个测试样例的输出结果不再完全相同。现在假设去掉其中一个或多个教师模型,那可能会导致最终结果的不同。所以此时将存在隐私泄露的风险。


差分隐私防护技术的介绍与实践(下)

2-9 实例3教师模型的输出结果


使用numpy生成数据,执行pate分析,执行下列代码:

num_teachers, num_examples, num_possible_answers = (1001000100)                      

preds = np.random.randint(low=0, high=num_possible_answers, size=(num_teachers, num_examples)).astype(int)

indices = cal_max(preds, num_possible_answers)

data_dep_eps, data_ind_eps = pate.perform_analysis(teacher_preds=preds, indices=indices, noise_eps=5, delta=1e-5, moments=70)

print("Data Independent Epsilon:", data_ind_eps)

print("Data Dependent Epsilon:", data_dep_eps)

 

最后得到的输出证明了前面的想法。如图2-10所示,其中Data Dependent Epsilon Data Independent Epsilon 都较高。这意味着的确存在泄露隐私的风险。

差分隐私防护技术的介绍与实践(下)

2-10 实例3pate分析结果

 

实例4,实例3通过数据证明了在 ε 设置较高的情况下是存在隐私泄露风险的,所以在实例4需要将 ε 降低来验证差分隐私的确能够起到保护隐私的作用。还是使用同样的数据集,只是在pate分析时把noisy_eps 参数由原先的5,调到了0.0001。执行下列代码:

data_dep_eps, data_ind_eps = pate.perform_analysis(teacher_preds=preds, indices=indices, noise_eps=0.0001, delta=1e-5, moments=70)

print("Data Independent Epsilon:", data_ind_eps)

print("Data Dependent Epsilon:", data_dep_eps)

 

最后得到的输出证明了差分隐私的有效性,在调低了 ε 以后,Data Dependent Epsilon Data Independent Epsilon 都降了下来。如图2-11所示。

差分隐私防护技术的介绍与实践(下)

2-11 实例4pate分析结果

                  

通过上述四个实例,我们已经对PATE模型有了基本的了解了,PATE的核心就是将敏感的数据集拆分成多份,然后分别进入到独立的教师模型中训练,最后得到这些教师模型的学习参数。针对每一个教师模型产生的预测结果是不会单独公开的,必须通过增加噪声以后,通过RNM算法输出得票最高的结果。

 

2.5   PATE实践

前文已经对差分隐私保护做了详细的介绍。那么本节将通过一个深度学习实例来展开对PATE的实践。该案例就是深度学习中最常用的例子——手写数字的识别,训练和测试使用的数据则来自MNIST数据集。本节将MNIST数据集中部分数据当做敏感数据处理,将这部分敏感数据使用PATE中的教师模型去训练。而剩余的数据则当做公开数据,部分公开数据当成是没有标签的数据,让教师模型给出他们的隐私聚合后的预测值,再将预测值当成是这部分数据的标签。进一步的通过学生模型去训练这些数据,最终产生迁移了教师模型(敏感数据)知识的新模型。本案例同样在Google Colab 跑实验。由于篇幅关系,本节仅针对关键代码做解读,完整代码请直接参考附件1。下面对部分关键做解释:


1. 导入依赖包。

2. 下载并转换MNIST数据集,定义数据加载器。

3. 定义教师模型数量 num_teachers  250个,定义数据大小为240个,这是因为训练集(隐私数据)中共有60000个样本,平均分成250份给到教师模型训练,所以每一份有240个样本。

4. 按照每份240个样本的量对隐私数据进行拆分。

5. 设置模型对应的设备。

6. 定义分类器模型,后面教师模型将全部由该分类器初始化生成。


class Classifier(nn.Module):

"""

Forward Neural Network Architecture model

"""

def __init__(self):

super().__init__()

self.fc1 = nn.Linear(784256)

self.fc2 = nn.Linear(256128)

self.fc3 = nn.Linear(12864)

self.fc4 = nn.Linear(6410)

self.dropout = nn.Dropout(p=0.2# Dropout module with 0.2 drop probability

 

def forward(selfx):

x = x.view(x.shape[0], -1)

 

x = self.dropout(F.relu(self.fc1(x)))

x = self.dropout(F.relu(self.fc2(x)))

x = self.dropout(F.relu(self.fc3(x)))

x = F.log_softmax(self.fc4(x), dim=1)

return x

 

7. 250个教师模型进行训练,等待数分钟后,训练完成,保存所有的教师模型。

8. MNIST数据集中的测试集的90%用作学生模型的训练集,剩余10%用作测试集。

9. 构造学生模型的数据加载器

10. 定义教师模型的预测函数


def perdict(modeldataloader):

"""

Perdicts labels for a dataset

Input: model and dataloader

"""

outputs = torch.zeros(0, dtype=torch.long).to(device)

model.to(device)

model.eval()

for image, labels in dataloader:

image, labels = image.to(device), labels.to(device) 

output = model(image)

ps = torch.argmax(torch.exp(output), dim=1)

outputs = torch.cat((outputs, ps))

return outputs

 

11. 设置隐私等级 ε = 0.25 ,以学生模型的训练集作为教师模型的输入来得到预测值,以此迁移教师模型的知识。对教师模型输出的预测值使用拉普拉斯分布来增加噪声,然后通过RNM算法输出得票率最高的结果,该结果作为训练数据的标签。


# Creating the Aggregated Teacher and Student labels by combining the predictions of Teacher models

epsilon = 0.25

preds = torch.zeros((len(models),9000), dtype=torch.long)

for i, model in enumerate(models):

results = perdict(model, student_trainloader)

preds[i] = results

labels = np.array([]).astype(int)

for image_preds in np.transpose(preds): 

label_counts = np.bincount(image_preds, minlength = 10)

beta = 1/ epsilon

for i in range(len(label_counts)):

label_counts[i] += np.random.laplace(0, beta, 1)

new_label = np.argmax(label_counts)

labels = np.append(labels, new_label)

 

12. 使用学生模型对重新标注的数据进行训练,以此来不暴露隐私的情况下获得教师模型的知识。


epochs = 50

train_losses, test_losses = [], []

model = Classifier()

model.to(device)

criterion = nn.NLLLoss()

optimizer = optim.Adam(model.parameters(), lr=0.0001)

running_loss = 0

for e in range(epochs):

running_loss = 0

for images, labels in student_trainloader:

images, labels = images.to(device), labels.to(device)

optimizer.zero_grad()

log_ps = model(images)

loss = criterion(log_ps, labels)

loss.backward()

optimizer.step()

running_loss += loss.item()

else:

test_loss = 0

accuracy = 0

with torch.no_grad():

for images, labels in student_testloader:

images, labels = images.to(device), labels.to(device)

log_ps = model(images)

test_loss += criterion(log_ps, labels)

ps = torch.exp(log_ps)

top_p, top_class = ps.topk(1, dim=1)

equals = top_class == labels.view(*top_class.shape)

accuracy += torch.mean(equals.type(torch.FloatTensor))

train_losses.append(running_loss/len(student_trainloader))

test_losses.append(test_loss/len(student_testloader))

print("Epoch: {}/{}.. ".format(e+1, epochs),

"Training Loss: {:.3f}.. ".format(running_loss/len(student_trainloader)),

"Test Loss: {:.3f}.. ".format(test_loss/len(student_testloader)),

"Test Accuracy: {:.3f}".format(accuracy/len(student_testloader)))

 

13. 将训练数据的损失和测试损失通过图的形式展示出来,将得到如图2-12所示的结果。

差分隐私防护技术的介绍与实践(下)

2-12 损失对比


经过PATE模型的差分隐私保护后,测试的准确率最终达到92.8%,其准确度虽然没有直接训练时的高,但是仍然是能够接受的,毕竟这种方法能够更好的保护隐私。

 


[1] Matt Fredrikson, Somesh Jha, and Thomas Ristenpart. 2015. Model Inversion Attacks that Exploit Confidence Information and Basic Countermeasures. In Proceedings of the 22nd ACM SIGSAC Conference on Computer and Communications Security (CCS '15). 

[2] Papernot, Nicolas Abadi, Martín Erlingsson, Úlfar Goodfellow, Ian Talwar, Kunal. (2016). Semi-supervised Knowledge Transfer for Deep Learning from Private Training Data. 

[3] https://towardsdatascience.com/understanding-differential-privacy-85ce191e198a

[4] http://www.cleverhans.io/privacy/2018/04/29/privacy-and-machine-learning.html


 附件1 


# -*- coding: utf-8 -*-

"""dp_example.ipynb

 

Automatically generated by Colaboratory.

 

Original file is located at

https://colab.research.google.com/drive/1eYov-YD-ds3tAJO8USpkQLukbAs93Vfd

"""

 

import torch

import numpy as np

from torchvision import datasets, transforms

 

transform = transforms.Compose([transforms.ToTensor(),

transforms.Normalize([0.5], [0.5])]) #Grey Scale Image

mnist_trainset = datasets.MNIST(root='./data', train=True, download=True, transform=transform) #private data

 

mnist_testset = datasets.MNIST(root='./data', train=False, download=True, transform=transform) #public data

 

train_loader = torch.utils.data.DataLoader(mnist_trainset, batch_size=64, shuffle=True)

 

test_loader = torch.utils.data.DataLoader(mnist_testset, batch_size=64, shuffle=True)

 

from torch.utils.data import Subset

 

num_teachers = 250

 

teacher_loaders = []

 

data_size = 240 # mnist_trainset/num_teachers

 

for i in range(num_teachers):

indices = list(range(i*data_size, (i+1) *data_size)) #creating subsets of 600 data_size

subset_data = Subset(mnist_trainset, indices)

loader = torch.utils.data.DataLoader(subset_data, batch_size=64, num_workers=2)

teacher_loaders.append(loader)

 

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

print("Active device:", device)

 

from torch import nn, optim

import torch.nn.functional as F

 

class Classifier(nn.Module):

"""

Forward Neural Network Architecture model

"""

def __init__(self):

super().__init__()

self.fc1 = nn.Linear(784256)

self.fc2 = nn.Linear(256128)

self.fc3 = nn.Linear(12864)

self.fc4 = nn.Linear(6410)

self.dropout = nn.Dropout(p=0.2# Dropout module with 0.2 drop probability

 

def forward(selfx):

x = x.view(x.shape[0], -1)

 

x = self.dropout(F.relu(self.fc1(x)))

x = self.dropout(F.relu(self.fc2(x)))

x = self.dropout(F.relu(self.fc3(x)))

 

x = F.log_softmax(self.fc4(x), dim=1)

 

return x

 

epochs = 10

models = []

for i in range(num_teachers):

model = Classifier()

model.to(device)

criterion = nn.NLLLoss()

optimizer = optim.Adam(model.parameters(), lr=0.0001)

running_loss = 0

teacher_loss = []

for e in range(epochs):

running_loss = 0

for images, labels in teacher_loaders[i]:

images, labels = images.to(device), labels.to(device)

optimizer.zero_grad()

log_ps = model(images)

loss = criterion(log_ps, labels)

loss.backward()

optimizer.step()

running_loss += loss.item()

teacher_loss.append(running_loss)

print("Training teacher: {}/{}.. ".format(i+1, num_teachers), "Training Loss: {:.3f}.. ".format(running_loss))

models.append(model)

 

#Creating the public dataset

student_traindata = Subset(mnist_testset, list(range(9000))) #90% of Test dat as train data

student_testdata = Subset(mnist_testset, list(range(900010000))) #10% of Test data as test data

 

student_trainloader = torch.utils.data.DataLoader(student_traindata, batch_size=64, shuffle=True)

student_testloader = torch.utils.data.DataLoader(student_testdata, batch_size=64, shuffle=True)

 

def perdict(modeldataloader):

"""

Perdicts labels for a dataset

Input: model and dataloader

"""

outputs = torch.zeros(0, dtype=torch.long).to(device)

model.to(device)

model.eval()

for image, labels in dataloader:

image, labels = image.to(device), labels.to(device) 

output = model(image)

ps = torch.argmax(torch.exp(output), dim=1)

outputs = torch.cat((outputs, ps))

return outputs

 

# Creating the Aggregated Teacher and Student labels by combining the predictions of Teacher models

epsilon = 0.25

preds = torch.zeros((len(models),9000), dtype=torch.long)

for i, model in enumerate(models):

results = perdict(model, student_trainloader)

preds[i] = results

labels = np.array([]).astype(int)

for image_preds in np.transpose(preds): 

label_counts = np.bincount(image_preds, minlength = 10)

beta = 1/ epsilon

for i in range(len(label_counts)):

label_counts[i] += np.random.laplace(0, beta, 1)

new_label = np.argmax(label_counts)

labels = np.append(labels, new_label)

 

student_labels = np.array(labels)

preds = preds.numpy()

 

epochs = 50

train_losses, test_losses = [], []

model = Classifier()

model.to(device)

criterion = nn.NLLLoss()

optimizer = optim.Adam(model.parameters(), lr=0.0001)

running_loss = 0

for e in range(epochs):

running_loss = 0

for images, labels in student_trainloader:

images, labels = images.to(device), labels.to(device)

optimizer.zero_grad()

log_ps = model(images)

loss = criterion(log_ps, labels)

loss.backward()

optimizer.step()

running_loss += loss.item()

else:

test_loss = 0

accuracy = 0

with torch.no_grad():

for images, labels in student_testloader:

images, labels = images.to(device), labels.to(device)

log_ps = model(images)

test_loss += criterion(log_ps, labels)

ps = torch.exp(log_ps)

top_p, top_class = ps.topk(1, dim=1)

equals = top_class == labels.view(*top_class.shape)

accuracy += torch.mean(equals.type(torch.FloatTensor))

train_losses.append(running_loss/len(student_trainloader))

test_losses.append(test_loss/len(student_testloader))

 

print("Epoch: {}/{}.. ".format(e+1, epochs),

"Training Loss: {:.3f}.. ".format(running_loss/len(student_trainloader)),

"Test Loss: {:.3f}.. ".format(test_loss/len(student_testloader)),

"Test Accuracy: {:.3f}".format(accuracy/len(student_testloader)))

 

差分隐私防护技术的介绍与实践(下) 

原文始发于微信公众号(山石网科安全技术研究院):差分隐私防护技术的介绍与实践(下)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年5月31日17:53:02
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   差分隐私防护技术的介绍与实践(下)http://cn-sec.com/archives/1071714.html

发表评论

匿名网友 填写信息