Pytorch

发布于 2021 年 04 月 26 日 | 更新于 2021 年 09 月 01 日

安装

Anaconda

.condarcchannels是有顺序(优先级)的:

channels:
  - pytorch
  - conda-forge
  - main

conda install会从上往下找所需的包,找到了就会安装

如果main放在pytorch前面,就算按照官网的命令安装GPU版,也会装成CPU版,因为main里面只有CPU版的pytorch

要安装GPU版,把pytorch仓库放在main前面即可

后端(backends)检查

doc

cuDNN

版本:torch.backends.cudnn.version()

是否可用:torch.backends.cudnn.is_available()

是否启用:torch.backends.cudnn.enabled

CUDA

查看pytorch是否为支持cuda的版本torch.backends.cuda.is_built()

Returns whether PyTorch is built with CUDA support. Note that this doesn’t necessarily mean CUDA is available; just that if this PyTorch binary were run a machine with working CUDA drivers and devices, we would be able to use it. 返回True只能表示该pytorch(版本)可以支持cuda,实际能不能用cuda还要看系统是否安装了cuda环境

torch.save

后缀格式

知乎

首先讲讲保存模型或权重参数的后缀格式,权重参数和模型参数的后缀格式一样,pytorch中最常见的模型保存使用 .pt 或者是 .pth 作为模型文件扩展名。还有其他的保存数据的格式为.t7或者.pkl格式。t7文件是沿用torch7中读取模型权重的方式,而pth文件是python中存储文件的常用格式,而在keras中则是使用.h5文件 。

腾讯云

要保存多个组件,请在字典中组织它们并使用torch.save()来序列化字典。PyTorch 中常见的保存checkpoint 是使用 .tar 文件扩展名。

小结:

保存模型.pt

保存Checkpoint(包含更多、更完整的信息,用于继续训练)用.tar

显存

参数量与计算量没有绝对的关系,例如比较fc与conv,前者参数量大,但计算量小,后者参数量小,但计算量大。

谷歌提出的MobileNet就以增大参数量为代价换取更小的计算量(空间换时间),从而提升运行速度。

显存占用 = 模型参数 + 计算产生的中间变量

  1. 模型参数

    与输入无关,模型初始化后就固定了

  2. 中间变量

    梯度与动量(optimizer反向传播时用到)

GPU并行下的错误定位

错误如下:

/opt/conda/conda-bld/pytorch_1607370172916/work/aten/src/ATen/native/cuda/ScatterGatherKernel.cu:312: operator(): block: [716,0,0], thread: [17,0,0] Assertion `idx_dim >= 0 && idx_dim < index_size && "index out of bounds"` failed.

/opt/conda/conda-bld/pytorch_1607370172916/work/aten/src/ATen/native/cuda/ScatterGatherKernel.cu:312: operator(): block: [716,0,0], thread: [28,0,0] Assertion `idx_dim >= 0 && idx_dim < index_size && "index out of bounds"` failed.

/opt/conda/conda-bld/pytorch_1607370172916/work/aten/src/ATen/native/cuda/ScatterGatherKernel.cu:312: operator(): block: [291,0,0], thread: [63,0,0] Assertion `idx_dim >= 0 && idx_dim < index_size && "index out of bounds"` failed.

CUDA error: device-side assert triggered

在运行命令前加上`CUDA_LAUNCH_BLOCKING=1,强制同步执行,从而显示出正确的错误定位。参考

您可以通过设置环境变量强制进行同步计算 CUDA_LAUNCH_BLOCKING=1。这在 GPU 上发生错误时非常方便。(使用异步执行时,直到实际执行操作后才会报告此类错误,因此堆栈跟踪不会显示请求的位置。)参考

当模型在GPU上运行的时候其实是没办法显示出真正导致错误的地方的(按照PyTorch Dev的说法:“Because of the asynchronous nature of cuda, the assert might not point to a full correct stack trace pointing to where the assert was triggered from.”即这是CUDA的特性,他们也没办法),所以可以通过将模型改成在CPU上运行来检查出到底是哪里出错(因为CPU模式下会有更加细致的语法/程序检查)。但是当训练网络特别大的时候,这个方法通常是不可行的,转到CPU上训练的话可能会花费很长时间[1]。参考

本例中真正的错误位置与异步的提示位置差了一行,加上CUDA_LAUNCH_BLOCKING=1后,显示了正确的位置。

错误原因是标签(target, gt)中出现了负值,从而导致数组越界。

nn.Transformer

https://pytorch.org/docs/master/nn.html#transformer-layers

设置默认GPU

  1. 官方不推荐使用torch.cuda.set_device(device),详见文档

Usage of this function is discouraged in favor of device. In most cases it’s better to use CUDA_VISIBLE_DEVICES environmental variable.

  1. torch.cuda.device(device):Context-manager that changes the selected device.
  2. os.environ['CUDA_VISIBLE_DEVICES'] = '3'通过环境变量来设置(官方推荐

尝试后发现:

卷积的bias

卷积后接BN的话,bias就不起作用(公式推导),设为False,减少计算量

参考

Tensor的复制

需要用=的时候,都加上.clone()

gather & scatter_

gather

函数声明:torch.gather(input, dim, index, out=None, sparse_grad=False) → Tensor

一句话解释:按照index –> 从input中找值 –> 替换掉index的值(生成大小跟index一样的新张量)

size限制:

  1. 除了dim维外(apart from dimension dim),index的size大小不得超过input(想象把index矩阵盖到input上,按dim方向挑选值。当dim维超过时,只是会选到重复元素;而其他维超过了,找不到对应向量,就不知道从哪选值)

outputsizeindex一致

同时看input和index中dim那一维的向量(比如二维矩阵:dim=0就是纵向量,dim=1就是横向量),从input中按照index来找值,找到的值填回index的对应位置,生成大小跟index一样的新张量

如dim=1,则根据向量index[i, :, j, ...]的元素值,在向量a[i, :, j, ...]中找值,找到的值填回index。想象把index盖到input上,

dim=1看,以中间的行为例,计算方式如下所示:(其余行同理)

gather

import torch
a = torch.arange(1., 10.).view(3,3)
print(a)
# tensor([[1., 2., 3.],
#         [4., 5., 6.],
#         [7., 8., 9.]])
index = torch.LongTensor([
    [1, 1, 1, 1, 1],
    [2, 0, 1, 2, 0],
    [1, 2, 0, 0, 2],
])
# output = torch.gather(input=a, dim=1, index=index)
a.gather(dim=1, index=index)  # 与上一行等价
# tensor([[2., 2., 2., 2., 2.],	
#         [6., 4., 5., 6., 4.],
#         [8., 9., 7., 7., 9.]])

pytorch中有很多类似的等价写法:func(input=a, **kwargs)等价于a.func(**kwargs)

官方计算公式:

out[i][j][k] = input[index[i][j][k]][j][k]  # if dim == 0
out[i][j][k] = input[i][index[i][j][k]][k]  # if dim == 1
out[i][j][k] = input[i][j][index[i][j][k]]  # if dim == 2

scatter_

函数声明:scatter_(dim, index, src) → Tensor

一句话解释:将src的值 –> 根据index(用于确定位置) –> 写到self里面(覆盖 or 替换self中相应位置的值)

公式:

self[index[i][j][k]][j][k] = src[i][j][k]  # if dim == 0
self[i][index[i][j][k]][k] = src[i][j][k]  # if dim == 1
self[i][j][index[i][j][k]] = src[i][j][k]  # if dim == 2

size限制:

  1. 除了dim维外(apart from dimension dim),index的size不得超过self(想象把index矩阵盖到self上,dim维超过了,只是会在同一位置重复填值,后填的值覆盖先填的;而其他维超过了,找不到对应向量,就不知道往哪填值)
  2. index的size不得超过src(想象把 index覆盖到src上,被覆盖的区域就会填到self里面)

output的size与self一致

个人认为理解这两个函数的难点在于:理解dim的含义

这里dim的含义与gather函数是相同的:可以将一个dim维的向量(行向量 or 列向量)当作独立子任务来操作。即选定一个dim维的向量,执行一套完整的计算流程(见一句话解释),再处理dim维的下一个向量

scatter_的计算过程与gather类似,都是围绕dim进行的,如上一段所诉。示例代码如下:

import torch
a = torch.zeros(3, 3)
print(f'a:\n{a}')
# tensor([[0., 0., 0.],
#         [0., 0., 0.],
#         [0., 0., 0.]])
src = torch.arange(1., 16.).view(3,5)
print(f'src:\n{src}')
# tensor([[ 1.,  2.,  3.,  4.,  5.],
#         [ 6.,  7.,  8.,  9., 10.],
#         [11., 12., 13., 14., 15.]])
index = torch.LongTensor([
    [1, 1, 1, 1],
    [2, 0, 1, 2],
    [2, 0, 1, 2],
])
a.scatter_(dim=1, index=index, src=src)
print(f'a:\n{a}')
# tensor([[ 0.,  4.,  0.],
#         [ 7.,  8.,  9.],
#         [12., 13., 14.]])

第一行为例:

  1. 把1(src)写到张量a的1(index)位置
  2. 把2(src)写到张量a的1(index)位置(覆盖)
  3. 把3(src)写到张量a的1(index)位置(覆盖)
  4. 把4(src)写到张量a的1(index)位置(覆盖)
  5. 最终张量a的1位置就是4(后面的覆盖了前面的)

总结

不同

  1. gather:从self里面提取出需要的元素,组成新的tensor

    scatter_:替换/修改 self 中的某些元素

  2. gather:将self中(被选中的)零散的元素聚集起来形成新的tensor

    scatter_:将集中在src中的元素分散self

相似

dim的含义:都可以将dim维上每个向量当作独立子任务来执行一遍完整的操作(gather/scatter_)

参考

[1] pytorch.gather/scatter_的用法

[2] 3分钟理解 pytorch 的 gather 和 scatter

expand

函数声明:expand(*sizes) → Tensor,属于类:torch.Tensor

将大小为1扩展至size指定的大小(原来大小不为1的维不能扩展)

size-1表示保持该维大小不变

不会生成分配新的内存,只是在原Tensor上创建一个新的视图(?)

kernel_size=1的卷积

kernel_size=1时,一维二维三维卷积有什么区别?

冻结参数

Pytorch自由载入部分模型参数并冻结

如何冻结BN:关于pytorch的BN,在训练的模型上增添新模块[只训练新模块]

apply

apply(fn: Callable[Module, None]) → T,属于类:torch.nn.Module

Applies fn recursively to every submodule (as returned by .children()) as well as self.