Time: 2019-11-19
教程作者:Théo Ryffel -
GitHub: [@LaRiffle](LaRiffle - Overview)
1. 上下文
联邦学习是非常令人激动且日益火热的机器学习技术,只在构建一种这样的系统:能够在分布式数据上学习。
想法是让数据仍然保持在数据生产者手里(也叫作_worker_
),此想法能够保护隐私和所有权,只是将模型在结点之间进行共享。
一个能够立马用上的应用是,在手机上书写文本时帮你预测下一个单词:而你并不想这些数据发送到中心化服务器上用于训练,比如你的短信。
联邦学习的兴起和数据隐私意识的觉醒有很大的关系,比如GDPR法案,强制数据保护。
谷歌和苹果已经开始大量投入资金研发这种技术,但是他们还没有公开自己的工具。
OpenMined相信任何人想要开发、部署机器学习模型应当能够花费少量的力气就能完成隐私保护。
我们构建了一个工具,一行代码就能加密数据,参考[使用SPDZ训练CNN](Training a CNN using SPDZ)。
现在我们发布的联邦学习框架,综合了PyTorch框架用于提供一个易用的接口来完成构建安全和可扩展的模型。
本章,我们将直接使用经典的案例,[使用PyTorch在MNIST上训练CNN模型](https://github.com/pytorch/examples/blob/master/mnist/main.py),然后使用PySyft框架将其改造为联邦学习方法。
本章将会深入讲解每一行代码改造背后的原理。
这个材料也可以参考[博客: 10行代码改造模型为联邦学习]((Federated Learning in 10 lines))。
开始吧。
2. 实验
1) 标准的PyTorch/TorchVision的包导入
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
然后是PySyft的准备:
import syft as sy # <-- NEW: import the Pysyft library
hook = sy.TorchHook(torch) # <-- NEW: hook PyTorch ie add extra functionalities to support Federated Learning
bob = sy.VirtualWorker(hook, id="bob") # <-- NEW: define remote worker bob
alice = sy.VirtualWorker(hook, id="alice") # <-- NEW: and alice
2) 定义学习任务的设定(超参数):
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 {}
这个写法可以作为使用PyTorch框架开发的标准流程。
用类来组织在PyTorch中的代码。
3) 数据加载并发送到节点
节点应该是worker更精准的翻译。
我们首先加载数据然后转换训练数据集为联邦学习用的数据集,并将数据集分割分发到多个节点。
PS. 数据集还是我们本机先有的,处理好发送到的其他节点,如何在其他节点上已经有了数据,远程预处理呢?这些目前还没看到方法。
使用方法.federate
,联邦数据集现在就交给了联邦型的DataLoader
,测试数据集保持不变。
改造训练数据集:
federated_train_loader = sy.FederatedDataLoader(
datasets.MNIST('../data', train=True, download=True, transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,),(0.3081,)) # 这是ImageNet数据集的方差和均值
])).federate((bob, alice)), # 将数据集分发到所有的指定节点,现在是联邦数据集了
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)
4) CNN模型准备
现在我们使用CNN模型,和官方样例相同。
MNIST数据集的单张图片大小是28x28x1
,所以按照下面的模型定义,单张图片最后输出的大小是4x4x50
。
28 x 28 ->(conv1) 24 x 24 ->(max_pool2d) 12 x 12 ->(conv2) 8 x 8 ->(max_pool2d) 4x4
是可以直接推算出来的。
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)
net = Net()
print(net)
'''
Net(
(conv1): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
(conv2): Conv2d(20, 50, kernel_size=(5, 5), stride=(1, 1))
(fc1): Linear(in_features=800, out_features=500, bias=True)
(fc2): Linear(in_features=500, out_features=10, bias=True)
)
'''
5) 定义训练和测试函数
训练过程
针对训练过程而言,因为训练数据是分布在alice
,bob
对应的节点上,每一个batch
,我们都需要将模型发送到正确的位置。
然后,在远程执行这些操作,语法和在本地训练PyTorch模型一致。
结束后,你将更新好的模型和损失函数值拿回来,然后想办法更新模型。
def train(args, model, device, federated_train_loader, optimizer, epoch):
model.train() # 设置模型为训练模式
for batch_index, (data, target) in enumerate(federated_train_loader):
model.send(data.location)
data, target = data.to(device), target.to(device) # 如果有GPU搬到GPU计算
optimizer.zero_grad()
output = model(data)
loss = F.nll_loss(output, target)
loss.backward() # 计算梯度
optimizer.step() # 更新参数
model.get() # 取回模型
if batch_index % args.log_interval == 0:
loss = loss.get() # 每隔一段时间取回loss看看
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)))
6) 训练流程跑起来
model.train(), model.eval()
只在特定的模型上有效果。不要理解为定义好模型就能训练了~~
%%time
model = Net().to(device)
optimizer = optim.SGD(model.parameters(), lr=args.lr) # TODO momentum is not supported at the moment
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")
训练log(节选):
Train Epoch: 4 [59520/60032 (99%)] Loss: 0.057204
Test set: Average loss: 0.0534, Accuracy: 9828/10000 (98%)
Train Epoch: 5 [0/60032 (0%)] Loss: 0.161142
Train Epoch: 5 [1920/60032 (3%)] Loss: 0.031823
可以看到在测试集上的平均损失是:0.0534,准确率为98%(训练4个epoch)。
非联邦学习的训练方法中,4个epoch的训练效果如下:
Train Epoch: 4 [56320/60000 (94%)] Loss: 0.029797
Train Epoch: 4 [56960/60000 (95%)] Loss: 0.016238
Train Epoch: 4 [57600/60000 (96%)] Loss: 0.090854
Train Epoch: 4 [58240/60000 (97%)] Loss: 0.088320
Train Epoch: 4 [58880/60000 (98%)] Loss: 0.097426
Train Epoch: 4 [59520/60000 (99%)] Loss: 0.006990
Test set: Average loss: 0.0348, Accuracy: 9897/10000 (99%)
3. 最后一件事情
相比较于普通的PyTorch训练方式,联邦学习训练方式耗时有多长呢?
计算时间上,是普通的训练方式的1.9倍,不过和后面我们要添加的特性来说这是不值一提的时间消耗。
总结
我们大概修改了10行左右的代码就完成了从官方PyTorch在MNIST数据集上的训练样例到真实的联邦学习设定的改造。
是的,还有很多可以改进的地方。我们可以让计算并行操作(毕竟是分布在多个节点上),以及执行联邦均值,每隔n个batch更新中心服务器上的模型以便减少通信消耗。
此外,还有许多新特性可以添加以使得联邦学习能够用在产品环境中,等到特性公布后我们会即时更新相关教程。
现在你应当能自己实现联邦学习模型了。
Peace & Love.
Enjoy.
END at 2019-11-19 9:24 pm