我们在这里介绍一个对于扩展工业级联邦学习至关重要的对象:计划。它大大减少了带宽使用,允许使用异步方案,并为远程设备提供了更多的自治权。计划的原始概念可以在论文大规模联合学习:系统设计中找到,但已在PySyft库中适应了我们的需求。
计划旨在像函数一样存储一系列的Torch操作,但它可以将该序列的操作发送给远程工作者,并保留对其的引用。这样,要对通过指针引用的某些远程输入上的 $n$ 操作序列进行远程计算,您现在需要发送包含计划的引用和指针的单个消息,而不是发送 $n$ 个消息。您还可以为函数提供张量(我们称为state tensors)以具有扩展的功能。可以将计划视为可以发送的函数,也可以视为可以远程发送和执行的类。因此,对于高级用户而言,计划的概念消失了,并被魔术功能所取代,该魔术功能允许向远程工作人员发送包含一系列任意的Torch函数。
需要注意的一件事是,您可以转换为计划的功能类别目前仅限于挂钩的Torch操作序列。即使我们正在努力尽快找到解决方法,这也特别排除了诸如if
,for
和while
语句之类的逻辑结构。 要完全精确,您可以使用这些,但是在计划的第一次计算中采用的逻辑路径(例如,第一个if
到False和for
中的5个循环)将是所有后续计算中保留的逻辑路径,在大多数情况下,我们都希望避免这种情况。
作者:
中文版译者:
In [ ]:
import torch
import torch.nn as nn
import torch.nn.functional as F
与PySyft专用部分相比,有一个重要说明:本地工作机不应该是客户工作机。 因为非客户工作人员可以存储对象,我们需要这种能力来运行计划。
In [ ]:
import syft as sy # import the Pysyft library
hook = sy.TorchHook(torch) # hook PyTorch ie add extra functionalities
# 重要: 本地工作机不应该是客户工作机
hook.local_worker.is_client_worker = False
server = hook.local_worker
我们定义远程工作机或devices,以与参考文章中提供的概念一致。 我们为他们提供一些数据。
In [ ]:
x11 = torch.tensor([-1, 2.]).tag('input_data')
x12 = torch.tensor([1, -2.]).tag('input_data2')
x21 = torch.tensor([-1, 2.]).tag('input_data')
x22 = torch.tensor([1, -2.]).tag('input_data2')
device_1 = sy.VirtualWorker(hook, id="device_1", data=(x11, x12))
device_2 = sy.VirtualWorker(hook, id="device_2", data=(x21, x22))
devices = device_1, device_2
In [ ]:
@sy.func2plan()
def plan_double_abs(x):
x = x + x
x = torch.abs(x)
return x
我们检查一下,是的现在有了一个计划!
In [ ]:
plan_double_abs
In [ ]:
pointer_to_data = device_1.search('input_data')[0]
pointer_to_data
如果我们告诉计划,它必须在devicelocation:device_1
上远程执行…,我们将收到错误消息因为计划尚未建立。
In [ ]:
plan_double_abs.is_built
要构建一个计划,您只需要在该计划上调用build
并传递执行该计划所需的参数(也就是一些数据)。当构建一个计划时,所有命令都由本地工作人员顺序执行,并被该计划捕获并存储在其read_plan
属性中!
In [ ]:
plan_double_abs.build(torch.tensor([1., -2.]))
In [ ]:
plan_double_abs.is_built
现在尝试发送,它正常工作了。
In [ ]:
# 这个单元成功运行
pointer_plan = plan_double_abs.send(device_1)
pointer_plan
与随后的张量一样,我们获得指向发送对象的指针。这里简称为 PointerPlan
。
要记住的重要一件事是,在构建计划时,我们会在计算之前预先设置应存储结果的ID,这将允许异步发送命令,已经具有对虚拟结果的引用,并且可以继续本地计算,而不必等待远程结果被计算出来。一个主要的应用是当您需要在device_1上进行批处理计算,而又不想等待该计算结束以在device_2上启动另一批处理计算时。
In [ ]:
pointer_to_result = pointer_plan(pointer_to_data)
print(pointer_to_result)
您可以简单地拿回值。
In [ ]:
pointer_to_result.get()
In [ ]:
class Net(sy.Plan):
def __init__(self):
super(Net, self).__init__()
self.fc1 = nn.Linear(2, 3)
self.fc2 = nn.Linear(3, 2)
def forward(self, x):
x = F.relu(self.fc1(x))
x = self.fc2(x)
return F.log_softmax(x, dim=0)
In [ ]:
net = Net()
In [ ]:
net
让我们使用一些模拟数据来构建计划。
In [ ]:
net.build(torch.tensor([1., 2.]))
现在,我们将计划发送给远程工作机。
In [ ]:
pointer_to_net = net.send(device_1)
pointer_to_net
让我们检索一些远程数据。
In [ ]:
pointer_to_data = device_1.search('input_data')[0]
然后,语法就像正常的远程顺序执行,也就是本地执行一样。但是,与传统的远程执行相比,每次执行仅进行一次通信。
In [ ]:
pointer_to_result = pointer_to_net(pointer_to_data)
pointer_to_result
我们照常得到结果!
In [ ]:
pointer_to_result.get()
等等! 我们已经看到了如何显着减少本地工作机(或服务器)与远程设备之间的通信!
In [ ]:
class Net(sy.Plan):
def __init__(self):
super(Net, self).__init__()
self.fc1 = nn.Linear(2, 3)
self.fc2 = nn.Linear(3, 2)
def forward(self, x):
x = F.relu(self.fc1(x))
x = self.fc2(x)
return F.log_softmax(x, dim=0)
In [ ]:
net = Net()
# 建立计划
net.build(torch.tensor([1., 2.]))
这是我们刚刚执行的主要步骤
In [ ]:
pointer_to_net_1 = net.send(device_1)
pointer_to_data = device_1.search('input_data')[0]
pointer_to_result = pointer_to_net_1(pointer_to_data)
pointer_to_result.get()
实际上,您可以从同一计划构建其他PointerPlan,因此语法与在另一台设备上远程运行计划的语法相同
In [ ]:
pointer_to_net_2 = net.send(device_2)
pointer_to_data = device_2.search('input_data')[0]
pointer_to_result = pointer_to_net_2(pointer_to_data)
pointer_to_result.get()
注意:当前,对于Plan类,您只能使用一种方法,并且必须将其命名为"forward"。
In [ ]:
@sy.func2plan(args_shape=[(-1, 1)])
def plan_double_abs(x):
x = x + x
x = torch.abs(x)
return x
plan_double_abs.is_built
参数args_shape
在内部用于创建具有给定形状的模拟张量,这些张量用于构建计划。
In [ ]:
@sy.func2plan(args_shape=[(1, 2), (-1, 2)])
def plan_sum_abs(x, y):
s = x + y
return torch.abs(s)
plan_sum_abs.is_built
您还可以为函数提供状态元素!
In [ ]:
@sy.func2plan(args_shape=[(1,)], state=(torch.tensor([1]), ))
def plan_abs(x, state):
bias, = state.read()
x = x.abs()
return x + bias
In [ ]:
pointer_plan = plan_abs.send(device_1)
x_ptr = torch.tensor([-1, 0]).send(device_1)
p = pointer_plan(x_ptr)
p.get()
要了解更多信息,您可以在教程Part 8-bias中发现我们如何使用带有协议的计划!
祝贺您完成本笔记本教程! 如果您喜欢此方法,并希望加入保护隐私、去中心化AI和AI供应链(数据)所有权的运动,则可以通过以下方式做到这一点!
帮助我们的社区的最简单方法是仅通过给GitHub存储库加注星标! 这有助于提高人们对我们正在构建的出色工具的认识。
我们编写了非常不错的教程,以更好地了解联合学习和隐私保护学习的外观,以及我们如何为实现这一目标添砖加瓦。
保持最新进展的最佳方法是加入我们的社区! 您可以通过填写以下表格来做到这一点http://slack.openmined.org
对我们的社区做出贡献的最好方法是成为代码贡献者! 您随时可以转到PySyft GitHub的Issue页面并过滤“projects”。这将向您显示所有概述,选择您可以加入的项目!如果您不想加入项目,但是想做一些编码,则还可以通过搜索标记为“good first issue”的GitHub问题来寻找更多的“一次性”微型项目。
如果您没有时间为我们的代码库做贡献,但仍想提供支持,那么您也可以成为Open Collective的支持者。所有捐款都将用于我们的网络托管和其他社区支出,例如黑客马拉松和聚会!
In [ ]: