syft是一个隐私保护的深度学习框架,核心的操作基于命令链和tensor。支持FL、MPC和差分隐私DP。创新点如下:
PySyft是用于安全和隐私深度学习的Python库,它在主流深度学习框架(例如PyTorch和TensorFlow)中使用联邦学习,差分隐私和加密计算(例如多方计算(MPC)和同态加密(HE))将隐私数据与模型训练分离。
torch是什么
torch就是诸多深度学习框架中的一种
业界有几大深度学习框架:1)tensorflow,谷歌主推,时下最火,小型试验和大型计算都可以,基于python,缺点是上手相对较难,速度一般;2)torch,facebook主推,用于小型试验,开源应用较多,基于lua,上手较快,网上文档较全,缺点是lua语言相对冷门;3)mxnet,大公司主推,主要用于大型计算,基于python和R,缺点是网上开源项目较少;4)caffe,大公司主推,用于大型计算,基于c++、python,缺点是开发不是很方便;5)theano,速度一般,基于python,评价很好
张量通常包含数据,数据可能包含隐私信息,很多时候计算的任务不能独立完成,需要借助第三方,在这个过程中,必须保留数据持有者对数据的操作权,PySyft就是基于这个思想,提出了张量指针(PointerTensor)的概念。
import torch
import syft as sy
hook = sy.TorchHook(torch)
bob = sy.VirtualWorker(hook, id="bob")
x = torch.tensor([1,2,3,4,5])
y = torch.tensor([1,1,1,1,1])
x_ptr = x.send(bob)
y_ptr = y.send(bob)
z_ptr = x_ptr + x_ptr
z = z.get()
#将x、y发送到远程机器上,同时可以操作,get回之后,远程机器上数据同时销毁
这里的x_ptr和y_ptr都不是实际数据,但却可以执行加法操作,事实上这里是发送了一个操作到远程机器,让远程机器在数据上执行加法,而其产生的结果也是一个指针,指向的是保留在远程机器上的结果,通过get()获取其真实数据,并且在获取后,远程的bob将失去这个数据,这就是将数据所有权归还给了本地——数据所有权是传递的
z_ptr = x_ptr + x_ptr
z = z.get()
m_ptr = z_ptr+x_ptr # 错误!此时z_ptr已经失效
z_ptr = z.send(bob)
m_ptr = z_ptr+x_ptr # 重新发送后方可计算
当然实际环境是复杂的,并不是这么简单的收回远程机器就老老实实收回了,要实现安全的计算,还需要许多技术。
alice = sy.VirtualWorker(hook, id="alice")
bob = sy.VirtualWorker(hook, id="bob")
# alice和bob是远程的工作机
x = torch.tensor([1,2,3,4,5])
y = torch.tensor([1,1,1,1,1])
# x y都是本地的数据
z = x + y # z 也是本地的
# 将x发送到alice、y发送到bob
x_ptr = x.send(alice)
y_ptr = y.send(bob)
# 这一句不能执行,因为x_ptr是alice的数据,y_ptr是bob的数据
z = x_ptr + y_ptr
# 可以执行,x_ptr和y_ptr此时都在bob上
x_ptr = x.send(bob)
z_ptr = x_ptr+y_ptr
# 计划函数定义
@sy.func2plan(args_shape=[(1,)])
def inc_plan(x):
return x + 1
@sy.func2plan(args_shape=[(1,)])
def mul_plan(x):
return x * 2
# 创建协议
protocol = sy.Protocol([("worker1", inc_plan), ("worker2", mul_plan),])
# 协议部署
protocol.deploy(alice,bob)
# 运行
x = torch.tensor([1.,2.,3.,4.])
res_ptr = protocol.run(x)
res_ptr.get()
运行结果为 tensor([ 4., 6., 8., 10.])
现在worker1上运行inc_plan(加一操作),再在worker2上运行mul_plan(平方操作)
同态加密是一种加密技术,它允许在密文上进行计算操作,且恢复明文后得到正确计算结果。有加同态、乘同态、全同态等算法,分别对应在密文上进行加法、乘法、以及加法乘法操作。
x = torch.tensor([1,2,3,4]).share(alice,bob)
y = torch.tensor([2,3,4,5]).share(alice.bob)
z = x + y
z.get()
share()
方法就是秘密共享中的拆分操作,拆分后将结果发送给alice和bob,执行的加和操作也是在拆分后的密文上执行的,一直到本地工作机获取到结果为止,alice和bob都对原文不知情。
联邦学习:数据分离,通信加密。
本示例为模型平均的联邦学习
import torch
from torch import optim, nn
import syft as sy
import copy
hook = sy.TorchHook(torch)
# 创建一对工作机
bob = sy.VirtualWorker(hook, id='bob')
alice = sy.VirtualWorker(hook, id='alice')
# 中央服务器
secure_worker = sy.VirtualWorker(hook, id='secure_worker')
# 数据集
data = torch.tensor([[0,0],[0,1],[1,0],[1,1.]], requires_grad=True)
target = torch.tensor([[0],[0],[1],[1.]], requires_grad=True)
# 通过以下方式获取每个工作机的训练数据的指针
# 向bob和alice发送一些训练数据
bob_data = data[0:2].send(bob)
bob_target = target[0:2].send(bob)
alice_data = data[2:].send(alice)
alice_target = target[2:].send(alice)
# 建立模型
model = nn.Linear(2, 1)
# 设置epoch和iter数目
epochs = 10
worker_iters = 5
for epoch in range(epochs):
# 发送模型给工作机
bob_model = model.copy().send(bob)
alice_model = model.copy().send(alice)
# 给每个工作机设置优化器
bob_opt = optim.SGD(params=bob_model.parameters(), lr=0.1)
alice_opt = optim.SGD(params=alice_model.parameters(), lr=0.1)
# 并行进行训练两个工作机的模型
for worker_iter in range(worker_iters):
# 训练bob的模型
bob_opt.zero_grad()
bob_pred = bob_model(bob_data)
bob_loss = ((bob_pred - bob_target) ** 2).sum()
bob_loss.backward()
bob_opt.step()
bob_loss = bob_loss.get().data
# 训练alice的模型
alice_opt.zero_grad()
alice_pred = alice_model(alice_data)
alice_loss = ((alice_pred - alice_target) ** 2).sum()
alice_loss.backward()
alice_opt.step()
alice_loss = alice_loss.get().data
# 将训练好的模型都发送到中央服务器去
bob_model.move(secure_worker)
alice_model.move(secure_worker)
# 进行模型平均
with torch.no_grad():
model.weight.set_(((alice_model.weight.data + bob_model.weight.data) / 2).get())
model.bias.set_(((alice_model.bias.data + bob_model.bias.data) / 2).get())
print("bob loss: {}".format(bob_loss))
print("alice loss: {}".format(alice_loss))
叫做Secure Multi-Party Computation,简称SMPC,是一种非常奇怪的“加密”形式。 每个值都被分成多个“共享”,而不是使用公共/私有密钥对变量进行加密,每个共享都像私有密钥一样工作。 通常,这些“份额”将分配给2个或更多owners。 因此,为了解密变量,所有owners必须同意允许解密。 本质上,每个人都有一个私钥。
安全多方计算的真正非凡特性是能够在变量被加密的同时进行计算。
x = encrypt(25)
y = encrypt(5)
def add(x, y):
z = list()
# 第一个工作机将其共享分片相加
z.append((x[0] + y[0]) % Q)
# 第二个工作机将其共享分片相加
z.append((x[1] + y[1]) % Q)
# 第三个工作机将其共享分片相加
z.append((x[2] + y[2]) % Q)
return z
decrypt(*add(x,y))
输出为30
联邦学习(Federated Learning)是一种安全分布式深度学习技术,它允许各个数据持有者在不公开数据的情况下协同训练得到一个共享的模型,其目的是打破数据孤岛,在保护数据的隐私的前提下利用数据实现数据整合。
目前关于联邦学习的实现有许多说法,有梯度聚合、模型平均、选择上传等等。有的认为参数服务器持有模型,参与者不持有;有的认为是各个数据持有者持有模型,参数服务器不需要获取模型。众说纷纭。
但其核心是不变的:那就是数据分离,通信加密。
联邦学习的各个参与者,会在本地训练模型,然后每一轮(或者固定间隔的轮次)将其模型参数,或者梯度(广义梯度,即前一轮次与当前轮次的模型参数的差)上传到参数服务器,由参数服务器将各个参与者的上传参数进行聚合,得到的结果再返还给各个参与者,参与者更新本地模型后,继续训练。
在这个过程中,有如下几个计划:
模型训练,模型是需要训练的,这个操作必须由各个参与者执行
安全聚合,在梯度传递到参数服务器并返回给各个参与者这个过程中,传递的参数是不安全的,需要进行加密保护;并且,参数服务器要对参数进行聚合。
在MNIST数据集上的CNN训练范例演示如下:
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
import syft as sy
hook = sy.TorchHook(torch)
bob = sy.VirtualWorker(hook, id="bob")
alice = sy.VirtualWorker(hook, id="alice")
class Arguments():
def __init__(self):
self.batch_size = 64
self.test_batch_size = 1000
self.epochs = 10
self.lr = 0.01
self.momentum = 0.5
self.no_cuda = False
self.seed = 1
self.log_interval = 30
self.save_model = False
args = Arguments()
use_cuda = not args.no_cuda and torch.cuda.is_available()
torch.manual_seed(args.seed)
device = torch.device("cuda" if use_cuda else "cpu")
kwargs = {'num_workers': 1, 'pin_memory': True} if use_cuda else {}
federated_train_loader = sy.FederatedDataLoader(
datasets.MNIST('../data', train=True, download=True,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
]))
.federate((bob, alice)), # <-- NEW: we distribute the dataset across all the workers, it's now a FederatedDataset
batch_size=args.batch_size, shuffle=True, **kwargs)
test_loader = torch.utils.data.DataLoader(
datasets.MNIST('../data', train=False, transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])),
batch_size=args.test_batch_size, shuffle=True, **kwargs)
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 20, 5, 1)
self.conv2 = nn.Conv2d(20, 50, 5, 1)
self.fc1 = nn.Linear(4*4*50, 500)
self.fc2 = nn.Linear(500, 10)
def forward(self, x):
x = F.relu(self.conv1(x))
x = F.max_pool2d(x, 2, 2)
x = F.relu(self.conv2(x))
x = F.max_pool2d(x, 2, 2)
x = x.view(-1, 4*4*50)
x = F.relu(self.fc1(x))
x = self.fc2(x)
return F.log_softmax(x, dim=1)
def train(args, model, device, federated_train_loader, optimizer, epoch):
model.train()
for batch_idx, (data, target) in enumerate(federated_train_loader): # <-- now it is a distributed dataset
model.send(data.location) # <-- NEW: send the model to the right location
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
output = model(data)
loss = F.nll_loss(output, target)
loss.backward()
optimizer.step()
model.get() # <-- NEW: get the model back
if batch_idx % args.log_interval == 0:
loss = loss.get() # <-- NEW: get the loss back
print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
epoch, batch_idx * args.batch_size, len(federated_train_loader) * args.batch_size,
100. * batch_idx / len(federated_train_loader), loss.item()))
def test(args, model, device, test_loader):
model.eval()
test_loss = 0
correct = 0
with torch.no_grad():
for data, target in test_loader:
data, target = data.to(device), target.to(device)
output = model(data)
test_loss += F.nll_loss(output, target, reduction='sum').item() # sum up batch loss
pred = output.argmax(1, keepdim=True) # get the index of the max log-probability
correct += pred.eq(target.view_as(pred)).sum().item()
test_loss /= len(test_loader.dataset)
print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
test_loss, correct, len(test_loader.dataset),
100. * correct / len(test_loader.dataset)))
model = Net().to(device)
optimizer = optim.SGD(model.parameters(), lr=args.lr)
for epoch in range(1, args.epochs + 1):
train(args, model, device, federated_train_loader, optimizer, epoch)
test(args, model, device, test_loader)
if args.save_model:
torch.save(model.state_dict(), "mnist_cnn.pt")