1.代码
#FedAVG
'''
概述: 在本教程的第2部分中,我们使用了非常简单的联邦学习版本来训练模型。这要求每个数据所有者信任模型所有者才能看到其梯度。
说明: 在本教程中,我们将展示如何使用第3部分中的高级聚合工具来允许参数由可信的“安全工作机”聚合,然后将最终结果模型发送回模型所有者(我们)。
这样,只有安全工作机才能看到谁的模型参数来自谁。我们也许能够知道模型的哪些部分发生了更改,但是我们不知道哪个工作人员(Bob或Alice)进行了哪些更改,从而创建了一层隐私。
'''
import torch
import syft as sy
import copy
hook = sy.TorchHook(torch)
from torch import nn, optim
'''
第一步: 建立数据所有者
首先,我们将创建两个数据所有者(Bob和Alice),每个数据所有者拥有少量数据。 我们还将初始化一个名为“secure_worker”的安全机器。
实际上,这可以是安全的硬件(例如英特尔的SGX),也可以只是受信任的中介。
'''
# 创建一对工作机
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发送一些训练数据
bobs_data = data[0:2].send(bob)
bobs_target = target[0:2].send(bob)
alices_data = data[2:].send(alice)
alices_target = target[2:].send(alice)
'''第二步: 建立我们的模型
对于此示例,我们将使用简单的线性模型进行训练。
我们通常可以使用PyTorch的nn.Linear构造函数对其进行初始化。
也即联邦学习的初始化全局模型'''
# 初始化玩具模型
model = nn.Linear(2,1)
'''第三步:发送模型的拷贝给Alice和Bob
接下来,我们需要将当前模型的副本发送给Alice和Bob,以便他们可以对自己的数据集执行学习步骤。
即联邦学习的模型下发'''
#接下来,我们需要将当前模型的副本发送给Alice和Bob,以便他们可以对自己的数据集执行学习步骤。使用copy复制
bobs_model = model.copy().send(bob)
alices_model = model.copy().send(alice)
'''yTorch 中的优化器 SGD(Stochastic Gradient Descent,随机梯度下降)来优化名为 bobs_model 的模型参数。
optim.SGD:这是 PyTorch 中的优化器类,用于实现随机梯度下降算法。optim 是 PyTorch 中用于优化算法的模块,而 SGD 则表示使用随机梯度下降算法进行优化。
params=bobs_model.parameters():这里通过 bobs_model.parameters() 获取了模型 bobs_model 中的所有可学习参数,并将其传递给优化器。这些参数是模型中需要通过优化算法更新的权重和偏置。
lr=0.1:这是学习率(learning rate)的设置,它是一个超参数,用于控制每次参数更新的步长。
学习率越大,参数更新的幅度越大,训练过程中模型收敛的速度可能会更快,但也可能会导致训练不稳定或震荡。通常需要根据具体问题进行调整。
综合起来,这段代码的作用是创建了一个 SGD 优化器,用于优化 bobs_model 中的参数,学习率为 0.1。接下来,可以使用这个优化器来更新模型参数,使模型逐步优化以适应训练数据。
'''
bobs_opt = optim.SGD(params=bobs_model.parameters(),lr=0.1)
alices_opt = optim.SGD(params=alices_model.parameters(),lr=0.1)
'''第4步:训练鲍勃和爱丽丝的模型(并行)
与通过安全平均进行联邦学习的常规做法一样,每个数据所有者首先在本地对模型进行几次迭代训练,然后再对模型进行平均。'''
#与通过安全平均进行联邦学习的常规做法一样,每个数据所有者首先在本地对模型进行几次迭代训练,然后再对模型进行平均。
for i in range(10):
# 训练Bob的模型
bobs_opt.zero_grad()#Bob 模型的优化器调用了 zero_grad() 方法,用于清除之前计算的梯度,以避免梯度累积。
bobs_pred = bobs_model(bobs_data)#Bob 模型使用 bobs_data 输入数据进行前向传播,得到预测值 bobs_pred。
bobs_loss = ((bobs_pred - bobs_target) ** 2).sum()#计算 Bob 模型的预测值与目标值之间的损失,这里使用了均方误差(MSE)作为损失函数。
bobs_loss.backward()#通过调用 backward() 方法,PyTorch 自动计算了损失函数关于模型参数的梯度。
bobs_opt.step()#Bob 模型的优化器调用了 step() 方法,用于更新模型参数,使得损失函数尽可能地减小。
bobs_loss = bobs_loss.get().data#将损失值转移到主内存以便后续处理。
# 训练Alice的模型
alices_opt.zero_grad()
alices_pred = alices_model(alices_data)
alices_loss = ((alices_pred - alices_target) ** 2).sum()
alices_loss.backward()
alices_opt.step()
alices_loss = alices_loss.get().data
print("num "+str(i)+" Self__Bob:" + str(bobs_loss) + "Self__Alice:" + str(alices_loss))
'''
第5步:将两个更新的模型发送到安全工作机
现在,每个数据所有者都拥有部分受过训练的模型,是时候以安全的方式将它们平均在一起了。我们通过指示Alice和Bob将其模型发送到安全(可信)服务器来实现这一目标。
请注意,这种使用我们的API的方式意味着每个模型都直接发送到secure_worker。我们从未见过(指数据?)。'''
alices_model.move(secure_worker)
bobs_model.move(secure_worker)
'''第6步:模型平均
最后,此训练epoch(译者注:一个epoch表示全部训练数据完整训练一轮)的最后一步是将Bob和Alice的训练模型平均在一起,然后使用它来设置全局“模型”的值。'''
with torch.no_grad():#with torch.no_grad(): 是一个上下文管理器,它告诉 PyTorch 在下面的代码块中不需要进行梯度计算。
# 通常情况下,在执行模型推断或者参数更新等操作时,我们不需要计算梯度,因此可以使用 torch.no_grad() 来临时关闭梯度计算,以提高代码执行效率和减少内存消耗。
'''具体地说,model.weight.set_() 和 model.bias.set_() 是 PyTorch 中张量的方法,用于直接设置张量的值。在这里,model.weight 和 model.bias 分别代表模型的权重和偏置参数。
通过 ((alices_model.weight.data + bobs_model.weight.data) / 2).get() 计算了 Bob 和 Alice 模型权重参数的平均值,并将其设置为了模型的新权重;类似地,偏置参数也进行了相同的操作。
总而言之,with torch.no_grad(): 上下文管理器用于临时关闭梯度跟踪,以提高代码执行效率,而后面的代码则是将两个模型的参数平均值设置为了当前模型的参数。'''
model.weight.set_(((alices_model.weight.data + bobs_model.weight.data) / 2).get())
model.bias.set_(((alices_model.bias.data + bobs_model.bias.data) / 2).get())
#完整的训练流程
'''
冲洗并重复
现在,我们只需要对此进行多次迭代!'''
#iterations = 10 和 worker_iters = 5:定义了总共的迭代次数和每个参与者在每次迭代中执行的本地训练次数。
iterations = 10
worker_iters = 5
for a_iter in range(iterations):#总迭代次数
bobs_model = model.copy().send(bob)
alices_model = model.copy().send(alice)
bobs_opt = optim.SGD(params=bobs_model.parameters(), lr=0.1)
alices_opt = optim.SGD(params=alices_model.parameters(), lr=0.1)
for wi in range(worker_iters):
# 训练Bob的模型
bobs_opt.zero_grad()
bobs_pred = bobs_model(bobs_data)
bobs_loss = ((bobs_pred - bobs_target) ** 2).sum()
bobs_loss.backward()
bobs_opt.step()
bobs_loss = bobs_loss.get().data
# 训练Alice的模型
alices_opt.zero_grad()
alices_pred = alices_model(alices_data)
alices_loss = ((alices_pred - alices_target) ** 2).sum()
alices_loss.backward()
alices_opt.step()
alices_loss = alices_loss.get().data
alices_model.move(secure_worker)
bobs_model.move(secure_worker)
with torch.no_grad():
model.weight.set_(((alices_model.weight.data + bobs_model.weight.data) / 2).get())
model.bias.set_(((alices_model.bias.data + bobs_model.bias.data) / 2).get())
print("Bob:" + str(bobs_loss) + " Alice:" + str(alices_loss))
'''最后,我们想确保我们得到的模型学习正确,因此我们将在测试数据集上对其进行评估。
在这个玩具问题中,我们将使用原始数据,但在实践中,我们将希望使用新数据来了解模型对看不见的样本的泛化程度。'''
preds = model(data)
loss = ((preds - target) ** 2).sum()
print("final___"+str(preds))
print(target)
print(loss.data)
在这个玩具示例中,平均模型相对于本地训练的纯文本模型表现不佳,但是我们能够在不暴露每个工人的训练数据的情况下对其进行训练。我们还能够在可信任的聚合器上聚合每个工作人员的更新模型,以防止数据泄露给模型所有者。
在未来的教程中,我们的目标是直接使用梯度进行可信聚合,以便我们可以使用更好的梯度估计来更新模型并获得更强大的模型。