改造官方例子
为了能够实现想要的各种FL架构,我们必须学会怎么改造官方例子,使之能够自己聚合、训练,在自己聚合方面,有这样一个例子:
import torch
import copy
import syft as sy
from torch import nn
from torch import optim
hook = sy.TorchHook(torch)
# 工作机作为客户端,用于训练模型,安全工作机作为服务器,用于数据的整合及交流
Li = sy.VirtualWorker(hook, id='Li')
Zhang = sy.VirtualWorker(hook, id='Zhang')
secure_worker = sy.VirtualWorker(hook, id='secure_worker')
data = torch.tensor([[0, 1], [0, 1], [1, 0], [1, 1.]], requires_grad=True)
target = torch.tensor([[0], [0], [1], [1.]], requires_grad=True)
data_Li = data[0:2]
target_Li = target[0:2]
data_Zhang = data[2:]
target_Zhang = target[2:]
Li_data = data_Li.send(Li)
Zhang_data = data_Zhang.send(Zhang)
Li_target = target_Li.send(Li)
Zhang_target = target_Zhang.send(Zhang)
model = nn.Linear(2, 1)
# 定义迭代次数
iterations = 20
worker_iters = 5
for a_iter in range(iterations):
Li_model = model.copy().send(Li)
Zhang_model = model.copy().send(Zhang)
# 定义优化器
Li_opt = optim.SGD(params=Li_model.parameters(), lr=0.1)
Zhang_opt = optim.SGD(params=Zhang_model.parameters(), lr=0.1)
# 并行训练
for wi in range(worker_iters):
# 训练Li的模型
Li_opt.zero_grad()
Li_pred = Li_model(Li_data)
Li_loss = ((Li_pred - Li_target) ** 2).sum()
Li_loss.backward()
Li_opt.step()
Li_loss = Li_loss.get().data
# 训练Zhang的模型
Zhang_opt.zero_grad()
Zhang_pred = Zhang_model(Zhang_data)
Zhang_loss = ((Zhang_pred - Zhang_target) ** 2).sum()
Zhang_loss.backward()
Zhang_opt.step()
Zhang_loss = Zhang_loss.get().data
# 将更新的模型发送至安全工作机
Zhang_model.move(secure_worker)
Li_model.move(secure_worker)
# 模型平均
with torch.no_grad():
model.weight.set_(#此时Zhang和Li的model已经在安全工作机上了
((Zhang_model.weight.data + Li_model.weight.data) / 2).get())
model.bias.set_(
((Zhang_model.bias.data + Li_model.bias.data) / 2).get())
# 打印当前结果
print("Li:" + str(Li_loss) + "Zhang:" + str(Zhang_loss))
# 模型评估
preds = model(data)
loss = ((preds - target) ** 2).sum()
print(preds)
print(target)
print(loss.data)
这是一个简单的实现线性回归的pysyft例子。
其中关键的是,它用三个VirtualWorker
来模拟了架构,利用.move()
改变了模型的位置,然后再手动聚合。而我们要想用相同的思路对官方的例子进行改进,就必须了解pysyft的一些函数概念.federate() .send() .get()
等等。
federated_train_loader = sy.FederatedDataLoader(
datasets.MNIST('/minist_data', train=True, download=True,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
]))
.federate((qin, zheng)),
batch_size=args['batch_size'], shuffle=True
)
torchvision
是pytorch的一个图形库,它服务于PyTorch深度学习框架的,主要用来构建计算机视觉模型。torchvision.transforms
主要是用于常见的一些图形变换。以下是torchvision
的构成:
torchvision.datasets
: 一些加载数据的函数及常用的数据集接口;torchvision.models
: 包含常用的模型结构(含预训练模型),例如AlexNet、VGG、ResNet等;torchvision.transforms
: 常用的图片变换,例如裁剪、旋转等;torchvision.utils
: 其他的一些有用的方法。torchvision.transforms.Compose()
类:这个类的主要作用是串联多个图片变换的操作,也就是把里面的所有操作包起来一起执行。上述代码的剩下两行 也不难看出,分别是把图片转化为tensor和归一化的。
看了一篇这个,感觉dataset.MINIST()
最后返回的一个数据集列表
writer = SummaryWriter('LOGS/008log')
for i in range(10):
img, target = test_data[i]
writer.add_image('test_set', img, i)
writer.close()
他这样test_data[i]
就直接代表的一个转化成tensor的图片。我们接下来的分析基本上都以这个思路继续。
我们接着探究,为了搞清楚上面代码到底做了什么,一下测试代码:
for batch_idx, (data, target) in enumerate(federated_train_loader):
if batch_idx < 5:
print(batch_idx, type(data), data.location)
else:
break
0 <class 'torch.Tensor'> <VirtualWorker id:qin #objects:4>
1 <class 'torch.Tensor'> <VirtualWorker id:qin #objects:4>
2 <class 'torch.Tensor'> <VirtualWorker id:qin #objects:4>
3 <class 'torch.Tensor'> <VirtualWorker id:qin #objects:4>
4 <class 'torch.Tensor'> <VirtualWorker id:qin #objects:4>
哦,原来.federate()
并没有改变什么,数据类型仍然是上面转换过的tensor,但是其只是分别分发到了两个虚拟打工人qin和zheng上!下面的代码
print('qin\'object: {}'.format(qin._objects))
print('zheng\'object: {}'.format(zheng._objects))
表示,二人手上都有了数据,也就是说federate()
函数已经实现了分发(省去了我们一个一个send()
的麻烦)!又因为实际上我们能够通过指针进行操作,那么对于federated_train_loader
来说,数据在哪里并不担心,只需要和以前没有FL一样操作就可以了,其实这也是pysyft想达到的,尽可能简化操作。
现在看看我们手上有的东西:federated_train_loader
加载好了训练数据可供我们调用,只不过我们是通过下发指令到远程的方式,而test_loader
提供了测试数据给我们(在本地),最后可以用它来测试。