隐私计算与联邦学习
近年来,为强化个人隐私信息保护,国家相继颁布了《网络安全法》、《数据安全法》等法律法规,规范数据的管理和使用。在约束和规范市场的同时,也在某种程度上加剧了企业对于数据流通的合法合规性担忧,数据孤岛愈演愈烈,各个行业对用户隐私和数据安全的关注度显著提高。为此,研究如何在保护隐私和安全的前提下,解决数据孤岛问题实现数据共享需求越来越突出,隐私计算受到极大重视,联邦学习应运而生。
隐私计算其实是一堆“数据可用不可见”的技术集合。在腾讯发表的《隐私计算白皮书2021》[9]中,给隐私计算下了一个定义:隐私计算(Privacy Computing)是一种由两个或多个参与方联合计算的技术和系统,参与方在不泄露各自数据的前提下通过协作对他们的数据进行联合机器学习和联合分析。从技术机制来看,隐私计算可以分为几大技术路线:差分隐私、安全多方计算(密码学)、联邦学习及可信计算环境(通过硬件技术来对数据进行隔离保护,以Intel等厂商为代表,国内包括蚂蚁金服等公司将之集成到自己的隐私计算平台)和其他技术等。
本篇文章将探讨隐私计算和联邦学习的理论和实践。
多角度个性化的本地差分隐私
本地差分隐私(LDP)简介
随着互联网的普及,隐私泄露事件层出不穷,受到危害影响的人越来越多(诈骗电话邮件,疫情期间个人信息被曝光等等),加上国内外相关法律法规的陆续出台,隐私计算(隐私保护计算、隐私增强计算)受到了业界和学术界的极大关注,隐私计算究其根本是应对数据隐私保护需求与数据可用性之间的矛盾。相对于传统的差分隐私,本地差分隐私(local differential privacy,LDP)具有不需要可信中心的优点,但也面临着诸多新的挑战。这里我们将探讨用户-数值-属性等多角度个性化隐私保护需求对本地化差分隐私模型所带来的的问题和挑战,对现有的相关机制进行分类讨论,分析一下其特点和未来研究方向。
现有的隐私保护可以大体分为两大类:
①隐私数据跨系统发布/共享,比如联邦学习、多方安全计算、同态加密,每个独立的服务器拥有不可公开数据(如客户/业务的私密数据),有时候因为业务需求需要与其他机构分享数据才能分析出有用的信息,比如说信贷风险评估、医疗机构的数据共享等等,如果按照直观的方式,将各方数据都集中起来,那可以很方便地获得满意的效果,但是出于隐私保护的考虑,这样的方式是不被允许的,这个时候就可以利用【联邦学习、多方安全计算、同态加密】技术。这类场景可以总结为:不同机构掌握各自不同的数据,然后进行联合计算或者发布。
②终端用户隐私数据采集:服务器想要收集各个用户产生的数据,比如说淘宝APP收集淘宝用户的购买记录、位置信息等等,若服务器不可靠,或者用户本身不愿意被收集,那么就无法收集足量的数据进行下一步分析。目前常用的技术【联邦学习、多方安全计算、同态加密】更加关注跨系统的发布/共享,在终端用户采集数据方面这些技术的效果并不好,尤其是密码学技术:终端用户规模、变化较大,安全多方计算的代价增长非常快,还有新用户不断加入,因此不适合大规模场景。所以本地差分隐私在此场景下有很大潜力。
差分隐私与本地差分隐私
传统差分隐私[1]是用户把数据交给可信赖的服务器,服务器维护一个数据集/数据库(服务器可访问),除了服务器还有这些数据的使用者,通过一定的接口来查询这些数据,这个过程中为了某些用户的隐私,服务器在把查询结果返回给使用者之前会先加一定的噪声,数据使用者无法倒推得到真实数据,传统差分隐私依赖于服务器是可靠的。本地差分隐私[2]最大的差别是用户在提交数据给服务器之前已经加了一些噪声,即用户数据在本地进行了一些变换,只把变换结果交给了服务器,服务器即使是恶意的也没关系,因为它也无法推导出用户的真实数据。在这个过程中,变换机制要特别的设计,变换之后的数据不能变得毫无意义、不能使用,满足可用性的要求。
一个最简单的方案是二值频率估计,还有多值频数统计/直方图估计:k-RR、RAPPER等。但是这些方法仍然存在的问题和不足:
- 准确度难以保证:每个用户都会进行一定的变换,本身就会产生一些误差,当聚合在一起之后准确度更加难以保证;
- 输入造假:如果用户造假很难做变换;
- 每个用户等同对待:不同用户的隐私保护要求不一样,统一的保护标准可能会流失用户或者造成不好的效果。
多角度个性化隐私保护需求则是为了解决每个用户等同对待的痛点。
多角度个性化隐私保护需求
视角1:不同用户的隐私保护关切程度存在差异
视角2:数据取值范围内不同数值的敏感度存在差异
视角3:对于多维数据,不同维度的敏感度存在差异
多角度:“用户-取值-属性”多角度隐私敏感度差异
现有个性化LDP机制分析
视角1:不同用户的隐私保护关切程度存在差异
在本地差分隐私下,比较早的一个工作[3]是进行了一个了类型频率估计,其主要思路是:
- 每个隐私层级用户,分别完成估计
- 不同层级的估计结果最优化组合
- 数据重用机制(二次扰动)
每个用户可以选择一个隐私保护层级(代表强度,由隐私计算来反映)提交,服务器将相同等级的用户数据聚集得到多个子集进行估算,每个子集估算一个结果,最后再将所有估计结果进行优化组合。在这个过程中考虑数据使用者的隐私保护,比如说使用者只使用某个强度以上的数据,为了避免数据量太少影响精度,会对不满足强度的数据进行二次扰动。
问题/可研究的点:
- 和现实的用户个性化隐私保护存在差异
- 仅适用于少数频率估计、均值估计问题,不是普适化方法,针对复杂的计算问题,比如说数据是个集合/键值如何实现?
- 隐私定价/激励机制?
视角2:数据取值范围内不同数值的敏感度存在差异
问题/可研究的点:
- 为充分反应数值敏感度的个性化特征
- 仅适用于少数频率估计,有些效用高的频率估计机制没有对应的个性化方案
- 对于集合数据的解决方案效用过低
- 连续型数据等还没有解决方案
视角3:对于多维数据,不同维度的敏感度存在差异
目前还没有考虑不同属性敏感度差异的LDP方案
多角度:“用户-取值-属性”多角度隐私敏感度差异
问题更加复杂,目前还没有相关工作。
总结
- 个性化的主要动机在于增加隐私预算投入,提高结果的准确度,增强LDP被接受的程度
- 单独从用户视角、数值视角的个性化机制有一些相关工作,但是其个性化抽象模型和适用范围都有进一步改进的空间
- 如何反应不同属性的敏感度差异,还缺少相关工作
- 如果同时考虑两个/三个角度的个性化特点,更是具有挑战性
Intel SGX上实现可扩展的隐私保护机器学习系统[7]
本文段将探讨一下在工业界中如何保障数据隐私。下图是大数据AI应用的现状,数据安全可以有三种状态:数据的存储安全、传输安全以及计算安全,在大数据领域通常会使用一些大数据框架来保证数据安全,如在存储方面,使用HDFS、HBase等No-Sql数据库,在这方面可以使用加密技术保证相对安全;在数据传输方面,Spark、flink等计算框架本身具有分布式的属性,因此在网络传输时可以进行TLS加密;在本地数据计算时,需要将数据解压解密出来后才能进行后续的分析计算,而这个过程是缺少安全保护的,因此Intel SGX则是应用在这个环节让数据的计算也能运行在安全的环境中,这样的环境即称为TEE(可信执行环境)。
相关技术背景介绍
Intel SGX简介
Intel SGX是英特尔软件防护扩展的缩写,是目前被广泛测试、研究和部署的可信执行环境(TEE),实现系统中相对最小攻击面。英特尔软件防护扩展以及其他硬件的安全技术实现了对敏感数据领域中硬件级别的隐私保护,同时可以满足主流工作负载对内存的需求,兼顾了性能体验。
下图是SGX生态示意图,首先底层是支持SGX的硬件,在Intel SGX之上是SGX SDK,用户可以基于SDK构建自己的应用,但是已有的机器学习应用直接用SGX SDK编写工程量十分浩大,因此有一种技术Lib OS架构在底层操作系统和上层应用之间,这样应用程序甚至平台框架的源代码不需要作太多的更改即可无缝迁移到Lib OS之上运行在SGX中,从而对数据计算进行安全保护。所以SGX通过Lib OS来将应用程序在一定程度上无缝迁移的手段具有易用性和安全性的特性。
Analytics Zoo
Analytics Zoo 是在 Spark、TensorFlow、Keras 和 BigDL 之上构建的大数据分析+AI 平台,下图是Analytics Zoo的架构图。BigDL是Intel自研的高性能深度学习框架,该框架从功能上和现在主流框架例如 TensorFlow 相似,但是从编程模型上支持分布式并且通过 Spark 打通了数据处理和模型训练过程,满足端到端训练的需求[8]。将大数据平台和主流深度学习框架无缝合并到一个集成管道中,构建端到端大数据AI应用流水线(数据导入-特征处理-模型训练-推理),即能够在现有基于 Spark 与英特尔服务器的基础设施上无缝地运行主流深度学习框架和模型,使得用户可以根据需求使用深度学习框架做分布式模型训练或推理,方便地扩展到企业已有的大型 Apache Hadoop/Spark 集群。
Analytics Zoo PPML是其中的工作流模块之一,是基于SGX的大数据和机器学习隐私计算,其架构图如下所示,底层是上述提到的TEE(SGX)、LibOS以及安全库,如差分隐私、多方安全计算技术等,在这之上构建大数据分析、机器学习流水线,包括安全的数据访问、梯度同步以及横向和纵向的联邦学习,上层与合作方创建隐私学习案例。
Intel SGX助力联邦学习-打破数据孤岛
下方左图是一个联邦学习场景,有多个参与方,每个参与方都有自己的本地数据和模型,这些数据都不出域,模型可以放在SGX下进行本地训练——考虑到本地也可能有隐私保护的需求,通过分布式的方式将更新梯度上传到可信的第三方,即受SGX保护,代码无需进行更改。这里值得关注的是一个基于SGX的认证机制,所有计算环境包括参与方和第三方都要经过验证和鉴权
联邦学习方法FedAvg实现(PyTorch)
联邦学习概念的是在2016年由Google公司提出的[6],Google应用在了其一款键盘输入法产品中,输入法利用用户的打字习惯来训练模型,以便在用户输入上文提示最有可能的下文,提升用户的输入速度以及使用感。该场景下的机器学习是分布式的,具有以下的特点:
- 数据分布非独立同分布:各终端用户又去地区、文化等等的不同会导致不同的输入习惯,终端设备收集到的数据很可能不符合相同的分布,从而一个终端设备的数据不能代表全局的数据;
- 数据不平衡:有的用户使用手机时间很长,样本数据丰富,有的则反之,不平衡的数据会影响模型训练的效果;
- 用户隐私保护:数据可能涉及到用户个人信息等隐私信息,另外这些数据可能高度个性化,不希望被上传到中心服务器,因此模型的训练应该在不传输原始训练数据的前提下进行;
- 通信成本:终端设备的网络连接不一定稳定,并且通信成本高、速度慢等特点进一步限制了数据的传输,很难要求用户定期上传几G甚至十几G的数据;
因此一种可行的方法是,每个拥有数据的终端设备利用自己的数据训练局部模型,在训练过程中不同的设备之间相互通信,所有局部模型借助通信整合到一起形成一个全局模型,该全局模型仿佛是收集了所有数据之后训练得到的模型。这便是联邦学习(Federated Learning)的思想。通俗来讲,联邦学习(Federated Learning)结构由Server和若干Client组成,在联邦学习方法过程中,没有任何用户数据被发送到Server端,通过这种方式保护了用户的数据隐私。另外,通信中传输的参数是特定于改进当前模型的,因此一旦应用了他们,Server就没有理由存储它们,这进一步提高了安全性。
联邦学习的整体思路是”数据不动 模型动“。Server提供全局共享的模型,Client下载模型并训练自己的数据集,同时更新模型参数。在Server和Client的每一次通信中,Server将当前的模型参数分发给各个Client(或者说Client下载服务端的模型参数),经过Client的训练之后,将更新后的模型参数返回给Server,Server通过某种方法将聚合得到的N个模型参数融合成一个作为更新后的Server模型参数。以此循环。那么如何将局部模型整合成为全局模型是联邦学习的关键问题,FedAvg算法正是为此而提出的。
本节实战联邦学习算法,梳理其方法流程,完成pytorch的代码实现。
数据集划分
首先我们要为每个客户端分配数据,在实际场景中,每个客户端有自己独有的数据,这里为了模拟场景,手动划分数据集给每个客户端。
客户端之间的数据可能是独立分布IID,也可能是非独立同分布Non_IID的
以Minist数据集为例——0~9的手写数字数据集,独立分布IID的意思是每个客户端都拥有0~9的完整数据集,而非独立同分布Non_IID就是每个客户端可能只拥有一部分数据集,比如说一个只拥有0、1的数据集,一个只拥有2、3的数据集
可以参考提升联邦学习的效率和效果 - 雪琪的文章 - 知乎 https://zhuanlan.zhihu.com/p/108163485
def cifar_iid(dataset, num_users):
num_items = int(len(dataset)/num_users)
# num_items = 500 # 测试时使用
#print(num_items)
dict_users, all_idxs = {}, [i for i in range(len(dataset))]
for i in range(num_users):
dict_users[i] = set(np.random.choice(all_idxs, num_items, replace=False))
all_idxs = list(set(all_idxs) - dict_users[i])
print('cifar_iid is ok!')
return dict_users
def cifar_noniid(dataset, num_users):
num_items = int(len(dataset) / num_users) # 每个节点的图片总数
# print(num_items)
num_labels = 2 # 每个节点只包含两类图片
num_pics = (int)(num_items / num_labels) # 每个节点每类所包含的图片总数
# print(num_pics)
dict_users, idxs, per_labal_idxs = {}, [i for i in range(10)], {}
for i in range(10):
per_labal_idxs[i] = [i for i in range(i * 5000,(i+1) * 5000)]
for i in range(num_users):
# print(idxs)
random_labels = np.random.choice(idxs,2,replace=False)
random_label_1 = set(np.random.choice(per_labal_idxs[random_labels[0]], num_pics, replace=False))
per_labal_idxs[random_labels[0]] = list(set(per_labal_idxs[random_labels[0]]) - random_label_1)
if(len(per_labal_idxs[random_labels[0]]) == 0):
idxs.remove(random_labels[0])
random_label_2 = set(np.random.choice(per_labal_idxs[random_labels[1]], num_pics, replace=False))
per_labal_idxs[random_labels[1]] = list(set(per_labal_idxs[random_labels[1]]) - random_label_2)
if (len(per_labal_idxs[random_labels[1]]) == 0):
idxs.remove(random_labels[1])
dict_users[i] = set.union(random_label_1, random_label_2)
print('cifar_noniid is ok!')
return dict_users
联邦训练
Server初始化并共享模型的参数
# 搭建神经网络,这里建立的是一个8层的CNN,cifar10的测试集的准确率为80%以上。
net_glob = CNNCifar(args=args).to(args.device)
net_glob.train()
# copy weights
# 获取模型参数以共享
w_glob = net_glob.state_dict()
Server和Client通信
获取到共享的模型参数后,即可开始通信(本地的训练):
# epoch:rounds of training
for iter in range(args.epochs):
w_locals, loss_locals = [], []
m = max(int(args.frac * args.num_users), 1)
# 随机选取一部分Client,全部选择会增加通信量,且实验效果可能不好
idxs_users = np.random.choice(range(args.num_users), m, replace=False)
# 每个Client基于当前模型参数和自己的数据训练并更新模型,返回每个Client更新后的参数
for idx in idxs_users:
local = LocalUpdate(args=args, dataset=dataset_train, idxs=dict_users[idx])
w, loss = local.train(net=copy.deepcopy(net_glob).to(args.device))
w_locals.append(copy.deepcopy(w))
loss_locals.append(copy.deepcopy(loss))
# update global weights
# 取平均值,得到本次通信中Server得到的更新后的模型参数
if(args.aggregate == 'avg'):
w_glob = FedAvg(w_locals)
elif(args.aggregate == 'median'):
w_glob = FedMedian(w_locals)
# copy weight to net_glob
net_glob.load_state_dict(w_glob)
# print loss
loss_avg = sum(loss_locals) / len(loss_locals)
print('Round {:3d}, Average loss {:.3f}'.format(iter, loss_avg))
loss_train.append(loss_avg)
local = LocalUpdate(args=args, dataset=dataset_train, idxs=dict_users[idx])
表示Client端的训练函数如下:
Client端训练
class LocalUpdate(object):
def __init__(self, args, dataset=None, idxs=None):
self.args = args
self.loss_func = nn.CrossEntropyLoss()
self.selected_clients = []
self.ldr_train = DataLoader(DatasetSplit(dataset, idxs), batch_size=self.args.local_bs, shuffle=True)
def train(self, net):
# 在训练模型时会在前面加上
net.train()
# train and update
optimizer = torch.optim.SGD(net.parameters(), lr=self.args.lr, momentum=0.5)
epoch_loss = []
for iter in range(self.args.local_ep):
batch_loss = []
for batch_idx, (images, labels) in enumerate(self.ldr_train):
images, labels = images.to(self.args.device), labels.to(self.args.device)
net.zero_grad() # 将模型的参数梯度初始化为零
log_probs = net(images) # 前向传播计算预测值(Server共享的模型)
loss = self.loss_func(log_probs, labels) # 计算当前损失
loss.backward() # 反向传播计算梯度
optimizer.step() # 更新所有参数
if self.args.verbose and batch_idx % 10 == 0:
print('Update Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
iter, batch_idx * len(images), len(self.ldr_train.dataset),
100. * batch_idx / len(self.ldr_train), loss.item()))
batch_loss.append(loss.item())
epoch_loss.append(sum(batch_loss)/len(batch_loss))
# 返回当前Client基于自己的数据训练得到的新的模型参数
return net.state_dict(), sum(epoch_loss) / len(epoch_loss)
FedAvg算法
这算法就是做了一个简单的平均
def FedAvg(w):
w_avg = copy.deepcopy(w[0])
for k in w_avg.keys():
for i in range(1, len(w)):
w_avg[k] += w[i][k]
w_avg[k] = torch.true_divide(w_avg[k], len(w))
return w_avg
测试
训练结束之后,我们要通过测试集来验证方法的泛化性,注意,虽然训练时,Server没有得到过任何一条数据,但是联邦学习最终的目的还是要在Server端学习到一个鲁棒的模型,所以在做测试的时候,是在Server端进行的,如下:
def test_img(net_g, datatest, args):
# 在测试模型时在前面使用:
net_g.eval()
# testing
test_loss = 0
correct = 0
data_loader = DataLoader(datatest, batch_size=args.bs, shuffle=False)
l = len(data_loader)
for idx, (data, target) in enumerate(data_loader):
if args.gpu != -1:
data, target = data.cuda(), target.cuda()
log_probs = net_g(data)
# sum up batch loss
test_loss += F.cross_entropy(log_probs, target, reduction='sum').item()
# get the index of the max log-probability
y_pred = log_probs.data.max(1, keepdim=True)[1]
correct += y_pred.eq(target.data.view_as(y_pred)).long().cpu().sum()
test_loss /= len(data_loader.dataset)
accuracy = 100.00 * correct / len(data_loader.dataset)
if args.verbose:
print('\nTest set: Average loss: {:.4f} \nAccuracy: {}/{} ({:.2f}%)\n'.format(
test_loss, correct, len(data_loader.dataset), accuracy))
return accuracy, test_loss
参考文献
[1]Dwork C, Kenthapadi K, McSherry F, et al. Our data, ourselves: Privacy via distributed noise generation[C]//Annual International Conference on the Theory and Applications of Cryptographic Techniques. Springer, Berlin, Heidelberg, 2006: 486-503.
[2]Kasiviswanathan S P, Lee H K, Nissim K, et al. What can we learn privately?[J]. SIAM Journal on Computing, 2011, 40(3): 793-826.
[3]Yiwen N I E, Yang W, Huang L, et al. A utility-optimized framework for personalized private histogram estimation[J]. IEEE Transactions on Knowledge and Data Engineering, 2018, 31(4): 655-669.
[4]Mangat N S. An improved randomized response strategy[J]. Journal of the Royal Statistical Society: Series B (Methodological), 1994, 56(1): 93-95.
[5]Murakami T, Kawamoto Y. Utility-optimized local differential privacy mechanisms for distribution estimation[C]//28th {USENIX} Security Symposium ({USENIX} Security 19). 2019: 1877-1894.
[6]McMahan B, Moore E, Ramage D, et al. Communication-efficient learning of deep networks from decentralized data[C]//Artificial intelligence and statistics. PMLR, 2017: 1273-1282.
[7]Dongjie Shi. https://www.slidestalk.com/AnalyticsZoo/AnalyticsZoo_PPML
[8]Dai J J, Wang Y, Qiu X, et al. Bigdl: A distributed deep learning framework for big data[C]//Proceedings of the ACM Symposium on Cloud Computing. 2019: 50-60.
[9]《隐私计算白皮书(2021年)》. http://www.xinhuanet.com/tech/20210720/5d6dfb1c9804413f93441ab6c4424af5/c.html
评论 (0)