mnist数据集训练_Part 06 在MNIST数据集上执行联邦学习训练CNN模型

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) 定义训练和测试函数

训练过程

针对训练过程而言,因为训练数据是分布在alicebob对应的节点上,每一个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

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值