pytorch基本概念

Pytorch几个基本概念的区分

1. numpy array 和 Tensor (CPU & GPU)

>>> import torch
>>> import numpy as np
>>> a = np.ones(5)
>>> a
array([1., 1., 1., 1., 1.])
>>> b = torch.from_numpy(a)     # numpy array-> CPU Tensor
>>> b 
tensor([1., 1., 1., 1., 1.], dtype=torch.float64)
>>> y = y.cuda()     # CPU Tensor -> GPU Tensor
>>> y
tensor([1., 1., 1., 1., 1.], device='cuda:0', dtype=torch.float64)
>>> y = y.cpu()  # GPU Tensor-> CPU Tensor
>>> y
tensor([1., 1., 1., 1., 1.], dtype=torch.float64)
>>> y = y.numpy()  # CPU Tensor -> numpy array
>>> y
array([1., 1., 1., 1., 1.])

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
>>> y = torch.from_numpy(y)
>>> y.to(device)   # 这里 x.to(device) 等价于 x.cuda()
tensor([1., 1., 1., 1., 1.], device='cuda:0', dtype=torch.float64)

索引、 view 是不会开辟新内存的,而像 y = x + y 这样的运算是会新开内存的,然后将 y 指向新内存。

2. Variable 和 Tensor (require_grad=True)

​ Pytorch 0.4 之前的模式为: Tensor 没有梯度计算,加上梯度更新等操作后可以变为Variable. Pytorch0.4 将 Tensor 和Variable 合并。默认 Tensor 的 require_grad 为 false,可以通过修改 requires_grad 来为其添加梯度更新操作。

>>> y
tensor([1., 1., 1., 1., 1.], dtype=torch.float64)  
>>> y.requires_grad
False
>>> y.requires_grad = True
>>> y
tensor([1., 1., 1., 1., 1.], dtype=torch.float64, requires_grad=True)

3. detach 和 with torch.no_grad()

一个比较好的 detach和 torch.no_grad区别的解释:

detach() detaches the output from the computationnal graph. So no gradient will be backproped along this variable.

torch.no_grad says that no operation should build the graph.

The difference is that one refers to only a given variable on which it’s called. The other affects all operations taking place within the with statement.

detach() 将一个变量从计算图中分离出来,也没有了相关的梯度更新。torch.no_grad()只是说明该操作没必要建图。不同之处在于,前者只能指定一个给定的变量,后者 则会影响影响在 with 语句中发生的所有操作。

4. model.eval() 和 torch.no_grad()

These two have different goals:

  • model.eval() will notify all your layers that you are in eval mode, that way, batchnorm or dropout layers will work in eval mode instead of training mode.

  • torch.no_grad() impacts the autograd engine and deactivate it. It will reduce memory usage and speed up computations but you won’t be able to backprop (which you don’t want in an eval script).

model.eval()torch.no_grad()的区别在于,model.eval()是将网络切换为测试状态,例如 BN 和随机失活(dropout)在训练和测试阶段使用不同的计算方法。torch.no_grad()是关闭PyTorch张量的自动求导机制,以减少存储使用和加速计算,得到的结果无法进行loss.backward()

5. model.train() 和 model.eval() 的不同

  • model.train() —— 训练时候启用, 会计算对应 Tensor 的梯度
    启用 BatchNormalization 和 Dropout,将 BatchNormalization 和 Dropout 置为 True
  • model.eval() —— 验证和测试时候启用, 不会计算对应 Tensor 的梯度
    不启用 BatchNormalization 和 Dropout,将 BatchNormalization 和 Dropout 置为 False

6. xx.data 和 xx.detach()

​ 在 0.4.0 版本之前, .data 的语义是 获取 Variable 的 内部 Tensor, 在 0.4.0 版本将 Variable 和 Tensor merge 之后, .data 和之前有类似的语义, 也是内部的 Tensor 的概念。x.datax.detach() 返回的 tensor 有相同的地方, 也有不同的地方:

相同:

  • 都和 x 共享同一块数据
  • 都和 x 的 计算历史无关
  • requires_grad = False

不同:

  • y= x.data 在某些情况下不安全, 某些情况, 指的就是上述 inplace operation 的第二种情况, 所以, release note 中指出, 如果想要 detach 的效果的话, 还是 detach() 安全一些.
>>> import torch
>>> x = torch.FloatTensor([[1., 2.]])
>>> w1 = torch.FloatTensor([[2.], [1.]])
>>> w2 = torch.FloatTensor([3.])
>>> w1.requires_grad = True
>>> w2.requires_grad = True
>>> d = torch.matmul(x, w1)
>>> d_ = d.data
>>> f = torch.matmul(d, w2)
>>> d_[:] = 1
>>> f.backward()

如果需要获取其值,可以使用 xx.cpu().numpy() 或者 xx.cpu().detach().numpy() 然后进行操作,不建议再使用 volatile和 xx.data操作。

7. ToTensor & ToPILImage 各自都做了什么?

ToTensor:

  • 取值范围: [0, 255] —> [0, 1.0]
  • NHWC —> NCHW
  • PILImage —> FloatTensor
# PIL.Image -> torch.Tensor.
tensor = torch.from_numpy(np.asarray(PIL.Image.open(path))
    ).permute(2, 0, 1).float() / 255
# 等价于
tensor = torchvision.transforms.functional.to_tensor(PIL.Image.open(path))

ToPILImage:

  • 取值范围: [0, 1.0] —> [0, 255]
  • NCHW —> NHWC
  • 类型: FloatTensor -> numpy Uint8 -> PILImage
# torch.Tensor -> PIL.Image.
image = PIL.Image.fromarray(torch.clamp(tensor * 255, min=0, max=255
    ).byte().permute(1, 2, 0).cpu().numpy())
# 等价于
image = torchvision.transforms.functional.to_pil_image(tensor)

8. torch.nn.xxx 与 torch.nn.functional.xxx

建议统一使用 torch.nn.xxx 模块,torch.functional.xxx 可能会在下一个版本中去掉。

torch.nn 模块和 torch.nn.functional 的区别在于,torch.nn 模块在计算时底层调用了torch.nn.functional,但 torch.nn 模块包括该层参数,还可以应对训练和测试两种网络状态。使用 torch.nn.functional 时要注意网络状态,如:

def forward(self, x):
    ...
    x = torch.nn.functional.dropout(x, p=0.5, training=self.training)

9. pytorch 中 torch.Tensor() 和 torch.tensor() 有什么相同点和区别?

  • 相同点

    都能用于生成新的张量

  • 不同点:

torch.Tensor()是 python类,更明确地说,是默认张量类型torch.FloatTensor() 的别名,torch.Tensor([1,2])会调用Tensor类的构造函数 _init_,生成单精度浮点类型的张量。

>>> a=torch.Tensor([1,2])
>>> a.type()
'torch.FloatTensor'
123

而 torch.tensor() 是 python 函数:https://pytorch.org/docs/stable/torch.html#torch.tensor ,函数原型是:

torch.tensor(data, dtype=None, device=None, requires_grad=False)

其中 data 可以是:list, tuple, NumPy ndarray, scalar 和其他类型。
torch.tensor 会从 data 中的数据部分做拷贝,根据原始数据类型生成相应的torch.LongTensor、torch.FloatTensor 和torch.DoubleTensor。

>>> a=torch.tensor([1,2])
>>> a.type()
'torch.LongTensor'
>>> a=torch.tensor([1.,2.])
>>> a.type()
'torch.FloatTensor'
>>> a=np.zeros(2,dtype=np.float64)
>>> a=torch.tensor(a)
>>> a.type()
'torch.DoubleTensor'

10. tensorflow 和 pytorch 构建网络的差异

[如果是比较 Tensorflow 和 Pytorch 的差异, 可以从: 上手难易程度、静态图vs动态图、调试、可视化、部署等方面进行分析]

图的动态定义 vs 静态定义

​ 两个框架都是在张量上进行运算,并将任意一个模型看成是有向非循环图(DAG),但是它们在其定义方面有很大的区别。

​ Tensorflow 基于静态图。 TensorFlow遵循“数据即代码,代码即数据”的理念。在TensorFlow中,你可以在模型能够运行之前静态地定义图, 然后在运行时将 数据 feed 到计算图中。与外部世界的所有通信都通过tf.Session对象和tf.Placeholder来执行,这两个张量在运行时会被外部数据替代。静态计算允许编译器进行更大程度的优化,但是其调试相对比较困难。

​ Pytorch 基于动态图, 在PyTorch中,图的定义则更为重要和动态化:你可以随时定义、随时更改、随时执行节点,并且没有特殊的会话接口或占位符。总体而言,该框架与Python语言集成地更为紧密,并且在大多数时候用起来感觉更加本地化,更加容易将大脑中的想法转化为程序.

​ 近年来的发展, tf 引入了 eager 模式实现动态图··, pytorch 也可以借助于 onnx、caffe2和 torchscript 来实现静态图, tensorflow 和 pytorch 的之间静态图和动态图的界限也逐渐变的模糊。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!