干货| PyTorch相比TensorFlow,存在哪些自身优势?

2017 年 10 月 4 日 全球人工智能

来源:1024深度学习

1,PyTorch课替代NumPy使用: PyTorch本身主要构件是张量 - 和NumPy看起来差不多。使得PyTorch可以支持大量相同的API,有时候可以把它的用作是NumPy的替代品.PyTorch的开发者们这样做的原因是希望这种框架可以完全获得GPU加速带来的便利,以便你可以快速进行数据预处理,或其他任何机器学习任务。将张量从NumPy转换至PyTorch非常容易,反之亦然。看看如下代码:

  
    
    
    
  1. import torch

  2. import numpy as np

  3. numpy_tensor = np.random.randn(10, 20)

  4. # convert numpy array to pytorch array

  5. pytorch_tensor = torch.Tensor(numpy_tensor)

  6. # or another way

  7. pytorch_tensor = torch.from_numpy(numpy_tensor)

  8. # convert torch tensor to numpy representation

  9. pytorch_tensor.numpy()

  10. # if we want to use tensor on GPU provide another type

  11. dtype = torch.cuda.FloatTensor

  12. gpu_tensor = torch.randn(10, 20).type(dtype)

  13. # or just call `cuda()` method

  14. gpu_tensor = pytorch_tensor.cuda()

  15. # call back to the CPU

  16. cpu_tensor = gpu_tensor.cpu()

  17. # define pytorch tensors

  18. x = torch.randn(10, 20)

  19. y = torch.ones(20, 5)

  20. # `@` mean matrix multiplication from python3.5, PEP-0465

  21. res = x @ y

  22. # get the shape

  23. res.shape  # torch.Size([10, 5])

2,从张量到变量:张量是PyTorch的一个完美组件,但是需要构建神经网络这还远远不够。反向传播怎么办?自动微分。为了支持这个功能,PyTorch提供了变量,在张量之上的封装如此,我们可以构建自己的计算图,并自动计算梯度每个变量实例都有两个属性:。包含初始张量本身的。数据,以及包含相应张量梯度的.grad

  
    
    
    
  1. import torch

  2. from torch.autograd import Variable

  3. # define an inputs

  4. x_tensor = torch.randn(10, 20)

  5. y_tensor = torch.randn(10, 5)

  6. x = Variable(x_tensor, requires_grad=False)

  7. y = Variable(y_tensor, requires_grad=False)

  8. # define some weights

  9. w = Variable(torch.randn(20, 5), requires_grad=True)

  10. # get variable tensor

  11. print(type(w.data))  # torch.FloatTensor

  12. # get variable gradient

  13. print(w.grad)  # None

  14. loss = torch.mean((y - x @ w) ** 2)

  15. # calculate the gradients

  16. loss.backward()

  17. print(w.grad)  # some gradients

  18. # manually apply gradients

  19. w.data -= 0.01 * w.grad.data

  20. # manually zero gradients after update

  21. w.grad.data.zero_()


你也许注意到我们手动计算了自己的梯度,这样看起来很麻烦,我们能使用优化器吗?当然。


  
    
    
    
  1. import torch

  2. from torch.autograd import Variable

  3. import torch.nn.functional as F

  4. x = Variable(torch.randn(10, 20), requires_grad=False)

  5. y = Variable(torch.randn(10, 3), requires_grad=False)

  6. # define some weights

  7. w1 = Variable(torch.randn(20, 5), requires_grad=True)

  8. w2 = Variable(torch.randn(5, 3), requires_grad=True)

  9. learning_rate = 0.1

  10. loss_fn = torch.nn.MSELoss()

  11. optimizer = torch.optim.SGD([w1, w2], lr=learning_rate)

  12. for step in range(5):

  13.    pred = F.sigmoid(x @ w1)

  14.    pred = F.sigmoid(pred @ w2)

  15.    loss = loss_fn(pred, y)

  16.    # manually zero all previous gradients

  17.    optimizer.zero_grad()

  18.    # calculate new gradients

  19.    loss.backward()

  20.    # apply new gradients

  21.    optimizer.step()

并不是所有的变量都可以自动更新。但是你应该可以从最后一段代码中看到重点:我们仍然需要在计算新梯度之前将它们手动归零。这是PyTorch的核心理念之一。有时我们会不太明白为什么要这么做,但另一方面,这样可以让我们充分控制自己的梯度。


3,静态图vs动态图: PyTorch和TensorFlow的另一个主要区别在于其不同的计算图表现形式.TensorFlow使用静态图,这意味着我们是先定义,然后不断使用它在PyTorch中,每次正向传播都会定义一个新计算图。在开始阶段,两者之间或许差别不是很大,但动态图会在你希望调试代码,或定义一些条件语句时显现出自己的优势。就像你可以使用自己最喜欢的调试器一样!


你可以比较一下while循环语句的下两种定义 - 第一个是TensorFlow中,第二个是PyTorch中:

  
    
    
    
  1. import tensorflow as tf

  2. first_counter = tf.constant(0)

  3. second_counter = tf.constant(10)

  4. some_value = tf.Variable(15)

  5. # condition should handle all args:

  6. def cond(first_counter, second_counter, *args):

  7.    return first_counter < second_counter

  8. def body(first_counter, second_counter, some_value):

  9.    first_counter = tf.add(first_counter, 2)

  10.    second_counter = tf.add(second_counter, 1)

  11.    return first_counter, second_counter, some_value

  12. c1, c2, val = tf.while_loop(

  13.    cond, body, [first_counter, second_counter, some_value])

  14. with tf.Session() as sess:

  15.    sess.run(tf.global_variables_initializer())

  16.    counter_1_res, counter_2_res = sess.run([c1, c2])
       


  
    
    
    
  1. import torch

  2. first_counter = torch.Tensor([0])

  3. second_counter = torch.Tensor([10])

  4. some_value = torch.Tensor(15)

  5. while (first_counter < second_counter)[0]:

  6.    first_counter += 2

  7.    second_counter += 1


看起来第二种方法比第一个简单多了,你觉得呢?


4,模型定义:想在PyTorch中创建if / else / while复杂语句非常容易。先回到常见模型中,PyTorch提供了非常类似于Keras的,即开即用的构造函数:

神经网络包(NN)定义了一系列的模块,它可以粗略地等价于神经网络的层。模块接收输入变量并计算输出变量,但也可以保存内部状态,例如包含可学习参数的变量.nn包还定义了一组在训练神经网络时常用的损失函数。

  
    
    
    
  1. from collections import OrderedDict

  2. import torch.nn as nn

  3. # Example of using Sequential

  4. model = nn.Sequential(

  5.    nn.Conv2d(1, 20, 5),

  6.    nn.ReLU(),

  7.    nn.Conv2d(20, 64, 5),

  8.    nn.ReLU()

  9. )

  10. # Example of using Sequential with OrderedDict

  11. model = nn.Sequential(OrderedDict([

  12.    ('conv1', nn.Conv2d(1, 20, 5)),

  13.    ('relu1', nn.ReLU()),

  14.    ('conv2', nn.Conv2d(20, 64, 5)),

  15.    ('relu2', nn.ReLU())

  16. ]))

  17. output = model(some_input)

如果你想要构建复杂的模型,我们可以将nn.Module类子类化。当然,这两种方式也可以互相结合。

  
    
    
    
  1. from torch import nn

  2. class Model(nn.Module):

  3.    def __init__(self):

  4.        super().__init__()

  5.        self.feature_extractor = nn.Sequential(

  6.            nn.Conv2d(3, 12, kernel_size=3, padding=1, stride=1),

  7.            nn.Conv2d(12, 24, kernel_size=3, padding=1, stride=1),

  8.        )

  9.        self.second_extractor = nn.Conv2d(

  10.            24, 36, kernel_size=3, padding=1, stride=1)

  11.    def forward(self, x):

  12.        x = self.feature_extractor(x)

  13.        x = self.second_extractor(x)

  14.        # note that we may call same layer twice or mode

  15.        x = self.second_extractor(x)

  16.        return x

在__init__方法中,需要定义之后需要使用的所有层。在正向方法中,需要提出如何使用已经定义的层的步骤。而在反向传播上,和往常一样,计算是自动进行的。


5,自定义层:如果我们想要定义一些非标准反向传播模型要怎么办?这里有一个例子--XNOR网络:

在这里我们不会深入细节,如果你对其感兴趣,可以参考一下原始论文:https://arxiv.org/abs/1603.05279

与我们问题相关的是反向传播需要权重必须介于-1到1之间。在PyTorch中,这可以很容易实现:

  
    
    
    
  1. import torch

  2. class MyFunction(torch.autograd.Function):

  3.    @staticmethod

  4.    def forward(ctx, input):

  5.        ctx.save_for_backward(input)

  6.        output = torch.sign(input)

  7.        return output

  8.    @staticmethod

  9.    def backward(ctx, grad_output):

  10.        # saved tensors - tuple of tensors, so we need get first

  11.        input, = ctx.saved_variables

  12.        grad_output[input.ge(1)] = 0

  13.        grad_output[input.le(-1)] = 0

  14.        return grad_output

  15. # usage

  16. x = torch.randn(10, 20)

  17. y = MyFunction.apply(x)

  18. # or

  19. my_func = MyFunction.apply

  20. y = my_func(x)

  21. # and if we want to use inside nn.Module

  22. class MyFunctionModule(torch.nn.Module):

  23.    def forward(self, x):

  24.        return MyFunction.apply(x)

正如你所见,只需要定义两种方法:一个为正向传播,一个为反向传播。如果从正向通道访问一些变量,可以将它们存储在ctx变量中。注意:在此前的API正向/反向传播不是静态的,存储变量需要以self.save_for_backward(input)的形式,并以input,_ = self.saved_tensors的方式接入。


6,在CUDA上训练模型:曾经讨论过传递一个张量到CUDA上,但如果希望传递整个模型,可以通过调用.cuda()来完成,并将每个输入变量传递到.cuda()中。在所有计算后,需要用返回.cpu()的方法来获得结果。


同时,PyTorch也支持在源代码中直接分配设备:

  
    
    
    
  1. import torch

  2. ### tensor example

  3. x_cpu = torch.randn(10, 20)

  4. w_cpu = torch.randn(20, 10)

  5. # direct transfer to the GPU

  6. x_gpu = x_cpu.cuda()

  7. w_gpu = w_cpu.cuda()

  8. result_gpu = x_gpu @ w_gpu

  9. # get back from GPU to CPU

  10. result_cpu = result_gpu.cpu()

  11. ### model example

  12. model = model.cuda()

  13. # train step

  14. inputs = Variable(inputs.cuda())

  15. outputs = model(inputs)

  16. # get back from GPU to CPU

  17. outputs = outputs.cpu()

因为有些时候我们想在CPU和GPU中运行相同的模型,而无需改动代码,我们会需要一种封装:

  
    
    
    
  1. class Trainer:

  2.    def __init__(self, model, use_cuda=False, gpu_idx=0):

  3.        self.use_cuda = use_cuda

  4.        self.gpu_idx = gpu_idx

  5.        self.model = self.to_gpu(model)

  6.    def to_gpu(self, tensor):

  7.        if self.use_cuda:

  8.            return tensor.cuda(self.gpu_idx)

  9.        else:

  10.            return tensor

  11.    def from_gpu(self, tensor):

  12.        if self.use_cuda:

  13.            return tensor.cpu()

  14.        else:

  15.            return tensor

  16.    def train(self, inputs):

  17.        inputs = self.to_gpu(inputs)

  18.        outputs = self.model(inputs)

  19.        outputs = self.from_gpu(outputs)


7,权重初始化在TesnorFlow中权重初始化主要是在张量声明中进行的.PyTorch则提供了另一种方法:首先声明张量,随后在下一步里改变张量的权重。权重可以用调用火炬。这个决定或许并不直接了当当,但当你希望初始化具有某些相同初始化类型的层时,它就会变得有用。

  
    
    
    
  1. import torch

  2. from torch.autograd import Variable

  3. # new way with `init` module

  4. w = torch.Tensor(3, 5)

  5. torch.nn.init.normal(w)

  6. # work for Variables also

  7. w2 = Variable(w)

  8. torch.nn.init.normal(w2)

  9. # old styled direct access to tensors data attribute

  10. w2.data.normal_()

  11. # example for some module

  12. def weights_init(m):

  13.    classname = m.__class__.__name__

  14.    if classname.find('Conv') != -1:

  15.        m.weight.data.normal_(0.0, 0.02)

  16.    elif classname.find('BatchNorm') != -1:

  17.        m.weight.data.normal_(1.0, 0.02)

  18.        m.bias.data.fill_(0)

  19. # for loop approach with direct access

  20. class MyModel(nn.Module):

  21.    def __init__(self):

  22.        for m in self.modules():

  23.            if isinstance(m, nn.Conv2d):

  24.                n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels

  25.                m.weight.data.normal_(0, math.sqrt(2. / n))

  26.            elif isinstance(m, nn.BatchNorm2d):

  27.                m.weight.data.fill_(1)

  28.                m.bias.data.zero_()

  29.            elif isinstance(m, nn.Linear):

  30.                m.bias.data.zero_()


8,反向排除子图

当你希望保留模型中的某些层或者为生产环境做准备的时候,禁用某些层的自动梯度机制非常有用在这种思路下,PyTorch设计了两个标志:requires_grad和volatile。可以禁用当前层的梯度,但子节点仍然可以计算。第二个可以禁用自动梯度,同时效果沿用至所有子节点。

  
    
    
    
  1. import torch

  2. from torch.autograd import Variable

  3. # requires grad

  4. # If there’s a single input to an operation that requires gradient,

  5. # its output will also require gradient.

  6. x = Variable(torch.randn(5, 5))

  7. y = Variable(torch.randn(5, 5))

  8. z = Variable(torch.randn(5, 5), requires_grad=True)

  9. a = x + y

  10. a.requires_grad  # False

  11. b = a + z

  12. b.requires_grad  # True

  13. # Volatile differs from requires_grad in how the flag propagates.

  14. # If there’s even a single volatile input to an operation,

  15. # its output is also going to be volatile.

  16. x = Variable(torch.randn(5, 5), requires_grad=True)

  17. y = Variable(torch.randn(5, 5), volatile=True)

  18. a = x + y

  19. a.requires_grad  # False


8,训练过程: PyTorch还有一些其他卖点。如可以设定学习速度,让我们以特定规则进行变化。或者通过简单的训练标记允许/禁止批规范层和dropout。如果你想要做的话,让CPU和GPU的随机算子不同也是可以的。

  
    
    
    
  1. # scheduler example

  2. from torch.optim import lr_scheduler

  3. optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

  4. scheduler = lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1)

  5. for epoch in range(100):

  6.    scheduler.step()

  7.    train()

  8.    validate()

  9. # Train flag can be updated with boolean

  10. # to disable dropout and batch norm learning

  11. model.train(True)

  12. # execute train step

  13. model.train(False)

  14. # run inference step

  15. # CPU seed

  16. torch.manual_seed(42)

  17. # GPU seed

  18. torch.cuda.manual_seed_all(42)

同时,还可以添加模型信息,或存储/加载一小段代码。如果你的模型是由OrderedDict或基于类的模型字符串,它的表示会包含层名。

  
    
    
    
  1. from collections import OrderedDict

  2. import torch.nn as nn

  3. model = nn.Sequential(OrderedDict([

  4.    ('conv1', nn.Conv2d(1, 20, 5)),

  5.    ('relu1', nn.ReLU()),

  6.    ('conv2', nn.Conv2d(20, 64, 5)),

  7.    ('relu2', nn.ReLU())

  8. ]))

  9. print(model)

  10. # Sequential (

  11. #   (conv1): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))

  12. #   (relu1): ReLU ()

  13. #   (conv2): Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))

  14. #   (relu2): ReLU ()

  15. # )

  16. # save/load only the model parameters(prefered solution)

  17. torch.save(model.state_dict(), save_path)

  18. model.load_state_dict(torch.load(save_path))

  19. # save whole model

  20. torch.save(model, save_path)

  21. model = torch.load(save_path)

根据PyTorch文档,用state_dict()的方式存储文档更好。


9,记录:训练过程的记录是一个非常重要的部分。可惜,PyTorch目前还没有像Tensorboard这样的东西。所以你只能使用普通文本记录Python了,你也可以试试一些第三方库:

  • 记录:HTTPS://github.com/oval-group/logger

  • 蜡笔小新:HTTPS://github.com/torrvision/crayon

  • tensorboard_logger:HTTPS://github.com/TeamHG-Memex/tensorboard_logger

  • tensorboard-pytorch:HTTPS://github.com/lanpa/tensorboard-pytorch

  • Visdom:HTTPS://github.com/facebookresearch/visdom


10,掌控数据:你可能会记得TensorFlow中的数据加载器,甚至想要实现它的一些功能。对于我来说,我花了四个小时来掌握其中所有管道的执行原理。



我想在这里添加一些代码,但我认为上图足以解释它的基础理念了.PyTorch开发者不希望重新发明轮子,他们只是想要借鉴多重处理。为了构建自己的数据加载器,可以从火炬。 utils.data.Dataset继承类,并更改一些方法:

  
    
    
    
  1. import torch

  2. import torchvision as tv

  3. class ImagesDataset(torch.utils.data.Dataset):

  4.    def __init__(self, df, transform=None,

  5.                 loader=tv.datasets.folder.default_loader):

  6.        self.df = df

  7.        self.transform = transform

  8.        self.loader = loader

  9.    def __getitem__(self, index):

  10.        row = self.df.iloc[index]

  11.        target = row['class_']

  12.        path = row['path']

  13.        img = self.loader(path)

  14.        if self.transform is not None:

  15.            img = self.transform(img)

  16.        return img, target

  17.    def __len__(self):

  18.        n, _ = self.df.shape

  19.        return n

  20. # what transformations should be done with our images

  21. data_transforms = tv.transforms.Compose([

  22.    tv.transforms.RandomCrop((64, 64), padding=4),

  23.    tv.transforms.RandomHorizontalFlip(),

  24.    tv.transforms.ToTensor(),

  25. ])

  26. train_df = pd.read_csv('path/to/some.csv')

  27. # initialize our dataset at first

  28. train_dataset = ImagesDataset(

  29.    df=train_df,

  30.    transform=data_transforms

  31. )

  32. # initialize data loader with required number of workers and other params

  33. train_loader = torch.utils.data.DataLoader(train_dataset,

  34.                                           batch_size=10,

  35.                                           shuffle=True,

  36.                                           num_workers=16)

  37. # fetch the batch(call to `__getitem__` method)

  38. for img, target in train_loader:

  39.    pass

有两件事你需要事先知道:

  1. PyTorch的图维度和TensorFlow的不同。前者的是[Batch_size×channels×height×width]的形式,但如果你没有通过预处理步骤torchvision.transforms.ToTensor()进行交互,则可以进行转换。中还有很多有用小工具。

  2. 你很可能会使用固定内存的GPU,对此,你只需要对cuda()调用额外的标志async = True,并从标记为pin_memory = True的DataLoader中获取固定批次。


11,最终架构和总结:了解模型,优化器和很多其他细节湖,可以做个总结了:

这里有一段用于解读的伪代码:

  
    
    
    
  1. class ImagesDataset(torch.utils.data.Dataset):

  2.    pass

  3. class Net(nn.Module):

  4.    pass

  5. model = Net()

  6. optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

  7. scheduler = lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1)

  8. criterion = torch.nn.MSELoss()

  9. dataset = ImagesDataset(path_to_images)

  10. data_loader = torch.utils.data.DataLoader(dataset, batch_size=10)

  11. train = True

  12. for epoch in range(epochs):

  13.    if train:

  14.        lr_scheduler.step()

  15.    for inputs, labels in data_loader:

  16.        inputs = Variable(to_gpu(inputs))

  17.        labels = Variable(to_gpu(labels))

  18.        outputs = model(inputs)

  19.        loss = criterion(outputs, labels)

  20.        if train:

  21.            optimizer.zero_grad()

  22.            loss.backward()

  23.            optimizer.step()

  24.    if not train:

  25.        save_best_model(epoch_validation_accuracy)

 PyTorch的主要特点:

  1. 可以用来代替Numpy

  2. 原型设计非常快

  3. 调试和使用条件流非常简单

  4. 有很多方便且开箱即用的工具

PyTorch是一个正在快速发展的框架,还有一个富有活力的社区,你不妨可以尝试使用PyTorch,或许正是好时机。

资源:

http://pytorch.org/tutorials/

https://discuss.pytorch.org/


登录查看更多
15

相关内容

基于Lua语言的深度学习框架 github.com/torch
KGCN:使用TensorFlow进行知识图谱的机器学习
专知会员服务
81+阅读 · 2020年1月13日
一网打尽!100+深度学习模型TensorFlow与Pytorch代码实现集合
【书籍】深度学习框架:PyTorch入门与实践(附代码)
专知会员服务
163+阅读 · 2019年10月28日
开源书:PyTorch深度学习起步
专知会员服务
50+阅读 · 2019年10月11日
2019年机器学习框架回顾
专知会员服务
35+阅读 · 2019年10月11日
TensorFlow 2.0 学习资源汇总
专知会员服务
66+阅读 · 2019年10月9日
最新翻译的官方 PyTorch 简易入门教程
人工智能头条
10+阅读 · 2019年1月10日
从张量到自动微分:PyTorch入门教程
论智
9+阅读 · 2018年10月10日
一文读懂PyTorch张量基础(附代码)
数据派THU
6+阅读 · 2018年6月12日
教程 | 从头开始了解PyTorch的简单实现
机器之心
20+阅读 · 2018年4月11日
PyTorch 到底好用在哪里?
AI研习社
3+阅读 · 2017年10月27日
手把手教你由TensorFlow上手PyTorch(附代码)
数据派THU
5+阅读 · 2017年10月1日
干货 | PyTorch相比TensorFlow,存在哪些自身优势?
全球人工智能
10+阅读 · 2017年9月30日
教程 | 如何从TensorFlow转入PyTorch
机器之心
7+阅读 · 2017年9月30日
Arxiv
102+阅读 · 2020年3月4日
Universal Transformers
Arxiv
5+阅读 · 2019年3月5日
Embedding Logical Queries on Knowledge Graphs
Arxiv
3+阅读 · 2019年2月19日
Arxiv
19+阅读 · 2018年3月28日
Arxiv
7+阅读 · 2018年1月21日
VIP会员
相关VIP内容
KGCN:使用TensorFlow进行知识图谱的机器学习
专知会员服务
81+阅读 · 2020年1月13日
一网打尽!100+深度学习模型TensorFlow与Pytorch代码实现集合
【书籍】深度学习框架:PyTorch入门与实践(附代码)
专知会员服务
163+阅读 · 2019年10月28日
开源书:PyTorch深度学习起步
专知会员服务
50+阅读 · 2019年10月11日
2019年机器学习框架回顾
专知会员服务
35+阅读 · 2019年10月11日
TensorFlow 2.0 学习资源汇总
专知会员服务
66+阅读 · 2019年10月9日
相关资讯
最新翻译的官方 PyTorch 简易入门教程
人工智能头条
10+阅读 · 2019年1月10日
从张量到自动微分:PyTorch入门教程
论智
9+阅读 · 2018年10月10日
一文读懂PyTorch张量基础(附代码)
数据派THU
6+阅读 · 2018年6月12日
教程 | 从头开始了解PyTorch的简单实现
机器之心
20+阅读 · 2018年4月11日
PyTorch 到底好用在哪里?
AI研习社
3+阅读 · 2017年10月27日
手把手教你由TensorFlow上手PyTorch(附代码)
数据派THU
5+阅读 · 2017年10月1日
干货 | PyTorch相比TensorFlow,存在哪些自身优势?
全球人工智能
10+阅读 · 2017年9月30日
教程 | 如何从TensorFlow转入PyTorch
机器之心
7+阅读 · 2017年9月30日
相关论文
Arxiv
102+阅读 · 2020年3月4日
Universal Transformers
Arxiv
5+阅读 · 2019年3月5日
Embedding Logical Queries on Knowledge Graphs
Arxiv
3+阅读 · 2019年2月19日
Arxiv
19+阅读 · 2018年3月28日
Arxiv
7+阅读 · 2018年1月21日
Top
微信扫码咨询专知VIP会员