[toc]

本人博客https://qinzheng7575.github.io/

Pytorch与PySyft安装

基本上按照这个 教程 来就可以,注意的是要查看自己的CUDA版本,我的就是10.1,所有安装了CUDA版的pytorch。会像如下报很多包的缺失也没关系:

一个一个安装就好了,值得注意的是某些包安的时候可能安不上去,应该是因为依赖的关系,先装其它包就好了,就像我是最后安装Flask和requests的,要先把其它的包安装完。

实验代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import syft as sy
import torch
import sys
from torch.nn import Parameter
import torch.nn as nn
import torch.nn.functional as F

hook = sy.TorchHook(torch)
print(hook)
print(torch.tensor([1,2,3,4,5]))
x = torch.tensor([1,2,3,4,5])
print('x = ', x)
y = x+x
print('y = ', y)
bob = sy.VirtualWorker(hook, id='bob')
print('bob = ', bob)
x = torch.tensor([1,2,3,4,5])
y = torch.tensor([1,1,1,1,1])
#先展示下bob的objs
print('bob._objects = ', bob._objects)
x_ptr = x.send(bob)
y_ptr = y.send(bob)
print('bob._objects = ', bob._objects, 'after send')
print('x_ptr = ', x_ptr)
print('y_ptr = ', y_ptr)
print('x_ptr.location = ', x_ptr.location)
print('x_ptr.owner = ', x_ptr.owner)
z = x_ptr + y_ptr
print('z = ', z)
print('bob._objects = ', bob._objects, 'after add')
1
2
3
4
5
6
7
8
9
10
11
12
13
<syft.frameworks.torch.hook.hook.TorchHook object at 0x000001D659883348>
tensor([1, 2, 3, 4, 5])
x = tensor([1, 2, 3, 4, 5])
y = tensor([ 2, 4, 6, 8, 10])
bob = <VirtualWorker id:bob #objects:0>
bob._objects = {}
bob._objects = {6365454270: tensor([1, 2, 3, 4, 5]), 93154224848: tensor([1, 1, 1, 1, 1])} after send
x_ptr = (Wrapper)>[PointerTensor | me:74728596879 -> bob:6365454270]
y_ptr = (Wrapper)>[PointerTensor | me:41675391015 -> bob:93154224848]
x_ptr.location = <VirtualWorker id:bob #objects:2>
x_ptr.owner = <VirtualWorker id:me #objects:0>
z = (Wrapper)>[PointerTensor | me:89273399291 -> bob:40901367564]
bob._objects = {6365454270: tensor([1, 2, 3, 4, 5]), 93154224848: tensor([1, 1, 1, 1, 1]), 40901367564: tensor([2, 3, 4, 5, 6])} after add

还是先理解一些基本函数是在做什么

初始化

我们先初始化所需的基本东西:

1
2
3
import torch
import syft as sy
hook = sy.TorchHook(torch)#增加额外的功能

发送tensor

然后创建一个远程的虚拟打工人(==卑微的小qin==),并创建一些数据,才能分发给他

1
2
3
4
5
qin = sy.VirtualWorker(hook=hook,id='qin')

data = torch.tensor([0, 1, 2, 1, 2])#创建tensor数据
data_ptr = data.send(qin)#指针指向这个数据
print(data_ptr)

(Wrapper)>[PointerTensor | me:15176171534 -> qin:98939698572]

看到这个指针,从我me(pysyft自动生成的打工人)指向了qin,并且每个打工人还有一个id

现在qin拥有了我们给他发的tensor。我们能用qin._objects来看qin现在有了哪些东西:

1
print(qin._objects)

{98939698572: tensor([0, 1, 2, 1, 2])}

发现这个数据和data_ptr里面的一样,说明发送过去了

返还tensor

远处的打工人qin算好了数据,应该把数据传回来,我们通过.get()从远处的打工人那里拿

1
2
3
4
data = data_ptr.get()#取回数据
print(data)

print(qin._objects)#看看此时打工人qin手上有啥

tensor([0, 1, 2, 1, 2])
{}

但是我们仅仅发送取回数据显然不是机器学习,但是后面例子告诉你,我们可以用指针在pytorch里做算法。

通过指针张量(Pointer Tensor)做深度学习

1
2
3
4
a = torch.tensor([3.14, 6.28]).send(qin)
b = torch.tensor([6.14, 3.28]).send(qin)
c = a + b
print(c)

(Wrapper)>[PointerTensor | me:11768974079 -> qin:12164024826]

在机器上执行c = a + b的时候,一个指令下发给远处的qin,他创建了新的张量,然后给我们发回了一个指针 c ,使用这个API,我们就可以在原有的pytorch代码上,些许改变得到想要的结果。

1
2
3
4
5
6
7
8
9
train = torch.tensor([2.4, 6.2], requires_grad=True).send(qin)
label = torch.tensor([2, 6.]).send(qin)

loss = (train - label).abs().sum()
loss.backward()
train = train.get()

print(train)
print(train.grad)

tensor([2.4000, 6.2000], requires_grad=True)
tensor([1., 1.])

能够定义两个张量,让他们之间做算术,然后还能backward传梯度回来。

恭喜你已经会了初步的操作了,让我们来点有趣的吧.jpg

初步练习:以Minist识别为例

源码

我们假定一个场景,现在要训练一个手写字幕识别的网络,但是我们现在没有训练数据,现在有两个人在远处,叫qinzheng,他们手上是数据结合起来,恰好能够训练,但是分别训练各自的数据就无法达到效果。我们还需要注意隐私。

我们就用联邦学习的思想,组合他们的数据去训练一个很棒的模型,同时又不违反隐私。接下来,我们把MINIST的数据分成两部分给他们,来模拟真实场景。

必要的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
import logging
import syft as sy
import torchvision

hook = sy.TorchHook(torch)
qin = sy.VirtualWorker(hook=hook, id="qin")
zheng = sy.VirtualWorker(hook=hook, id="zheng")
#设定参数
args = {
'use_cuda': True,
'batch_size': 64,
'test_batch_size': 1000,
'lr': 0.01,
'log_interval': 10,
'epochs': 10
}
use_cuda = args['use_cuda'] and torch.cuda.is_available()
device = torch.device("cuda" if use_cuda else "cpu")

搭建所需网络

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# CNN网络
class Net(nn.module):
def __init__(self):
super(Net, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3, stride=1),
#输出26*26*32
nn.ReLU(),
nn.Conv2d(in_channels=32, out_channels=64,kernel_size=3, stride=1),
#输出24*24*64
nn.ReLU()
)
self.fc = nn.Sequential(
nn.Linear(in_features=64*12*12, out_features=128),
nn.ReLU(),
nn.Linear(in_features=128, out_features=10),
)
self.dropout = nn.Dropout2d(0.25) # 随机丢弃

def forward(self, x):
x = self.conv(x)#输入的时候是28*28*1,输出应该是24*24*64
x = F.max_pool2d(x, 2)#用步长为2的池化,输出12*12*64
x = x.view(-1, 64*12*12)#此时将其拉成一条直线进入全连接
x = self.fc(x)
x = F.log_softmax(x, dim=1)
return x

数据加载和发送

这里需要加载数据集,然后发往各个打工人那里。然后,迭代训练我们的远程模型。

讲道理接下来就按照正常的Minist步骤就好了,然而我自己还不是很熟悉minist。。。哎又要从头学

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 下面是训练数据,需要分发给远处打工人
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
)
# 下面是测试数据,在我们本地
test_loader = DataLoader(
datasets.MNIST('/minist_data', train=False,
transforms=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])),
batch_size=args['batch_size'], shuffle=True
)

定义训练过程

下面函数中的(data,target)是一对张量指针,从而使用.location确定我们要发送的正确位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def train(args, model, device, train_loader, optimizer, epoch):
model.train()
# 远程迭代
for batch_idx, (data, target) in enumerate(train_loader): # enumrate用来编序号
model = model.send(data.location) # 发送模型到远程
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
output = model(data)

loss = F.null_loss(output, target)
loss.backward()
optimizer.step()
# 以上都是发送命令给远程,下面是取回更新的模型

model.get()
if batch_idx % args['log_interval'] == 0: # 打印间隔时间
# 由于损失也是在远处产生的,因此我们需要把它取回来
loss = loss.get()
print('Train Epoch:{}[{}/{}({:.06f}%)]\tLoss:{:.06f}'.format(
epoch,
batch_idx * args['batch_size'],
len(train_loader)*args['batch_size'],
100.*batch_idx/len(train_loader),
loss.item()
)
)

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def test(model, device, test_loader):
model.eval()
'''返回model的返回值以字符串显示,使用PyTorch进行训练和测试时
一定注意要把实例化的model指定train/eval,eval()时,
框架会自动把BN和DropOut固定住,不会取平均,而是用训练好的值,不然的话,
一旦test的batch_size过小,很容易就会被BN层导致生成图片颜色失真极大!!'''
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()
'''nll_loss的解释请看
https://blog.csdn.net/qq_22210253/article/details/85229988
和https://www.cnblogs.com/ranjiewen/p/10059490.html'''
#进行预测最可能的分类
pred =output.argmax(dim=1,keepdim=True)
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)))

主函数:

1
2
3
4
5
6
model = Net().to(device)
optimizer = optim.SGD(model.parameters(), lr=args['lr'])
logging.info("开始训练!!\n")
for epoch in range(1, args['epochs']+1):
train(args, model, device, federated_train_loader, optimizer, epoch)
test(model, device, test_loader)

最后:

Train Epoch:10[59520/60032(99.147122%)] Loss:0.022604

Test set: Average loss: 0.0000, Accuracy: 9821/10000 (98%)

==终于走到了这一步,这是联邦学习的一小步,也是自己毕设开始的一步,不管怎样,还是要对今天的自己说声辛苦了!12.13==

还没结束!

接下来要做的:

  1. 详细的看一遍代码,把每个函数都吃透,像这样
  2. 参照上面博客,做一下可视化、模型保存的东西。
  3. 基于学会的知识,用syft在别的机器学习问题上做一做,增加代码熟悉度。
  4. 暂时没想好,或许可以放一放pysyft的东西,考虑下毕设的数学模型了。