pytorch tutorial(持续更新)

pytorch tutorial

pytorch 的准备

安装流程

只需要在虚拟环境中安装 cuda 和 cudnn 即可,无需在真实环境中安装 cuda 和 cudnn(所以请不要自作主张跟着网上的教程下载 cuda 和 cudnn,已经试错)。

  1. 下载 miniconda,创建虚拟环境(python=3.11)。

  2. 下载显卡驱动,选择 LTS 版本。

  3. 注意CUDA、pytorch、cuDNN、python、显卡驱动、系统的版本匹配,不要用最新版本,要用 LTS 版本,以防其中一方无匹配版本(在 pytorch 官网指令下载的 cuda 和 pytorch 版本匹配,若与显卡驱动版本不匹配可查看历史版本)。

  4. 在虚拟环境中下载对应版本的 pytorch 和一些所需要的库:numpy、pandas、matplotlib...

  5. 在虚拟环境中下载 jupyter 包。

下载成果检验

  1. nvidia-smi可以获得当前显卡的驱动信息与支持 CUDA 的最高版本信息。

  2. nvcc -V可以获得目前已安装的 CUDA 的信息。

  3. 在虚拟环境中的 python 下检查安装的 pytorch、CUDA、cuDNN 的信息:

    import torch  
    print(torch.__version__) # pytorch 版本信息  
    print(torch.version.cuda) # cuda 版本信息  
    print(torch.backends.cudnn.version()) # cudnn 版本信息  
    

pytorch 核心讲解

pytorch 核心模块

torch核心模块

  1. _pycache_文件夹:存放 python 解释器生成的字节码,后缀通常是 pyc/pyo,节省了 python 语言转换成字节码的时间。

  2. _C文件夹:存放一系列 pyi 文件,检验数据类型,辅助 C 语言代码调用,pytorch 底层计算代码由 C++编写。

  3. include文件夹:存放 C++代码,头文件用.h/.hpp 文件存放,在 python 库中值包含头文件。

  4. lib文件夹:存放了大量的.lib 和.dll 文件(静态链接库和动态链接库),被各类顶层 python api 调用。

  5. autograd文件夹:pytorch 的核心模块和概念,实现了梯度自动求导。

  6. nn文件夹:搭建网络的网络层存放模块。

  7. onnx文件夹:pytorch 模型转换到 onnx 模型表示的核心模块,存放有大量的 opse**.py 文件。

  8. optim文件夹:优化模块,即存放优化使用的方法函数,以及学习率调整模块lr_scheduler.py

  9. util文件夹:包含各类常用工具,比如关键的 data 文件夹、tensorboard 文件夹等等。

torchvision核心模块

  1. datasets文件夹:官方的常用数据集的数据读取函数。

  2. models文件夹:存放了经典的、可复现的、有训练权重参数可下载的视觉模型、分割模型、检测模型、视频任务模型、量化模型,可供调用和参考。

  3. ops文件夹:视觉任务特殊的功能函数。

  4. transforms文件夹:数据增强库,是 pytorch 自带的图像预处理、增强、转换工具(可满足日常需求,复杂需求可以使用 Albumentations 库)。

torchaudio核心模块

pytorch 核心数据结构 —— Tensor

Tensor的介绍

  1. Tensor 即为张量。

  2. pytorch 中有两个有关张量的相关概念:

    • torch.Tensor是 tensor 的类,也是 pytroch 中的基本操作单位,类似于 np.array,但支持在 GPU 上进行计算。

    • torch.tensor是创建张量的一个函数,用于将数据变为张量形式的数据。

    • 但是一般用tensor表示张量这个数据结构。

  3. 在历史中 Tensor 需要经过 Variable 的包装才能实现自动求导,现 torch.Tensor 与 torch.autograd.Variable 已经合并,使得 Tensor;拥有了跟踪历史操作的功能,虽然 Variable 仍可用,但 Variable 返回值是一个 Tensor 而非原来的 Variable。

  4. Vairiable 不仅能对 Tensor 包装,而且还能记录生成 Tensor 的运算(自动求导的关键)。

  5. Tensor 的主要属性:

    • data:多维数组,最核心的属性。

    • dtype:多维数组的数据类型。

    • shape:多维数组的形状。

    • device:tensor 所在设备,cpu 或 cuda。

    • gradgrad_fnis_leafrequires_grad:同 Variable,是梯度计算中所用到的。

tensor相关函数

tensor 的创建
直接创建 tensor
  1. 直接创建 tensor 函数:torch.tensor

    '''  
    参数说明:  
    - data=array_like,创建 tensor 的初始数据。  
    - dtype=None/torch.dtype,tensor 的数据类型。  
    - device=None/torch.device,决定 tensor 位于 cpu 还是 gpu。  
    - required_grad=Bool,决定是否需要计算梯度,推荐用 torch.Tensor.requires_grad_()代替。  
    - pin_memory=Bool,是否将 tensor 存于锁页内存。  
    '''  
    
    # 以下有值的为默认值  
    torch.tensor(data, *, dtype=None, device=None, requires_grad=False, pin_memory=False)  
    
  2. 直接创建 tensor 函数:torch.from_numpy

    将 numpy 数组转换为 tensor,但是创建的 tensor 和原 array 共享同一块内存,即修改 array 或 tensor 数值,另一个都会改变。

    torch.from_numpy(ndarray)  
    
依数值创建 tensor
  1. 创建全 0tensor:torch.zeros

    '''  
    参数说明:  
    - *size=int sequence,决定形状。  
    - out=tensor,可以将函数返回的 tensor 赋给一个原有的 tensor,使得两个变量名指向同一个张量(同一块内存)。  
    - layout=torch.layout,表明张量在内存中的布局方式。  
    ...  
    '''  
    
    # 以下有值的为默认值  
    torch.zeros(*size, *, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)  
    
  2. 创建全 0 类似形状 tensor:torch.zeros_like(现已不支持关键字参数,仅支持 input,可以用 zeros 函数的 out 参数代替此函数)

    '''  
    参数说明:  
    - input=tensor,创建的 tensor 和 input 具有相同的形状。  
    ...  
    '''  
    
    # 以下有值的为默认值  
    torch.zeros_like(input, *, dtype=None, layout=None, device=None, requires_grad=False, memory_format=torch.preserve_format)  
    
    # 现已不支持关键字参数,仅支持 input,可以用 zeros 函数的 out 参数代替此函数  
    
  3. 创建全 1 和类似形状 tensor:torch.ones、torch.ones_like

    同上 zeros 与 zeros_like,且 ones_like 也已不支持关键字参数。

  4. 创建全填充 tensor:torch.full

    '''  
    参数说明:  
    - *size=int sequence,决定形状。  
    - fill_value=scalar,填充 tensor 的值。  
    - out=tensor,可以将函数返回的 tensor 赋给一个原有的 tensor。  
    ...  
    '''  
    
    # 以下有值的为默认值  
    torch.full(*size, fill_value, *, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)  
    
  5. 创建全填充类似形状 tensor:torch.full_like

    同上 zeros 和 ones,仅多 fill_value 参数与 memory_format 参数(=troch.memory_format,所需内存格式),但支持正常关键词参数,并未废除。

  6. 创建等差的一维张量:torch.arange

    '''  
    参数说明:  
    - start=int,数列起始值。  
    - end=int,数列结束值。  
    - step=int,数列等差值。  
    - out=tensor,可以将函数返回的 tensor 赋给一个原有的 tensor。  
    ...  
    '''  
    
    # 以下有值的为默认值  
    torch.arange(start=0, end, step=1, *, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)  
    
  7. 创建均分的一维张量:torch.linespace

    '''  
    参数说明:  
    - start=int,数列起始值。  
    - end=int,数列结束值。  
    - step=int,数列长度。  
    ...  
    '''  
    
    # 以下有值的为默认值  
    torch.linspace(start, end, steps, *, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)  
    
  8. 创建对数均分的一维张量:torch.logspace

    '''  
    参数说明:  
    - start=int,数列起始值为 base^start。  
    - end=int,数列结束值为 base^end。  
    - step=int,数列长度。  
    - base=float,对数函数的底。  
    ...  
    '''  
    
    # 以下有值的为默认值
    torch.logspace(start, end, steps, base=10.0, *, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)  
    
  9. 创建单位对角矩阵:torch.eye

    '''  
    参数说明:  
    - n=int,矩阵的行数。  
    - m=int,矩阵的列数,默认创建方阵。  
    ...  
    '''  
    
    # 以下有值的为默认值
    torch.eye(n, m=None, *, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)  
    
  10. 创建空(不进行初始化赋值)和类似形状空 tensor:torch.empty、torch.empty_like

    同上 zeros 与 zeros_like,但支持正常关键词参数,并未废除。

  11. 创建空张量,但可设置其在内存中的存储方式:torch.empty_strided

    '''  
    参数说明:  
    - stride=int tuple,张量存储在内存中的步长,即设置在内存中的存储方式。  
    ...  
    '''  
    
    # 以下有值的为默认值  
    torch.empty_strided(*size, stride, *, dtype=None, layout=None, device=None, requires_grad=False, pin_memory=False)  
    
依概率分布创建
  1. 创建高斯分布张量:torch.normal

    '''  
    参数说明:  
    - mean=tensor/float,高斯分布均值。  
    - std=tensor/float,高斯分布标准差。  
    - *size=int sequence,当 mean 和 std 全是标量时(存在),size 决定尺寸,否则由张量参数决定尺寸。  
    - generator=torch.Generator,用于采样的伪随机数生成器。  
    ...  
    '''  
    
    # 以下有值的为默认值  
    torch.normal(mean, std, *, generator=None, out=None)  
    
  2. 创建[0,1)均匀分布的张量:torch.rand

    '''  
    参数说明:  
    - *size=int sequence,决定形状。  
    ...  
    '''  
    
    # 以下有值的为默认值  
    torch.rand(*size, *, generator=None, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False, pin_memory=False)  
    
  3. 创建[0,1)均匀分布的类似形状张量:torch.rand_like

    同 torch.zeros_like 之于 torch.zeros,但未废除。

  4. 创建自定义区间均匀分布的张量:torch.randint

    '''  
    参数说明:  
    - low=int,下限。  
    - high=int,上限(开区间)。
    - size=tuple,张量的形状。  
    ...  
    '''  
    
    # 以下有值的为默认值  
    torch.randint(low=0, high, size, *, generator=None, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)  
    
  5. 创建自定义区间均匀分布的类似形状张量:torch.randint_like

    同 torch.zeros_like 之于 torch.zeros,但未废除。

  6. 创建标准正态分布的张量:torch.randn

    同 torch.rand 函数

  7. 创建标准正态分布的类似形状张量:torch.randn_like

    同 torch.zeros_like 之于 torch.zeros,但未废除。

  8. 创建 0 到 n-1 随机排列的张量:torch.randperm

    '''  
    参数说明:  
    - n=int,排列末项。   
    ...  
    '''  
    
    # 以下有值的为默认值  
    torch.randperm(n, *, generator=None, out=None, dtype=torch.int64, layout=torch.strided, device=None, requires_grad=False, pin_memory=False)
    
  9. 创建自定义概率的伯努利分布(0-1 分布)张量:torch.bernoulli

    '''  
    参数说明:  
    - input=tensor,分布的概率,即生成 1 的概率。   
    ...  
    '''  
    
    # 以下有值的为默认值  
    torch.bernoulli(input, *, generator=None, out=None)
    
tensor 相关操作函数
  1. rensor 的操作函数:

    方法 功能描述
    cat 将多个张量拼接在一起,例如多个特征图的融合可用。
    concat 同 cat, 是 cat()的别名。
    conj 返回共轭复数。
    chunk 将 tensor 在某个维度上分成 n 份。
    dsplit 类似 numpy.dsplit()., 将张量按索引或指定的份数进行切分。
    column_stack 水平堆叠张量。即第二个维度上增加,等同于 torch.hstack。
    dstack 沿第三个轴进行逐像素(depthwise)拼接。
    gather 高级索引方法,目标检测中常用于索引 bbox。在指定的轴上,根据给定的 index 进行索引。
    hsplit 类似 numpy.hsplit(),将张量按列进行切分。若传入整数,则按等分划分。若传入 list,则按 list 中元素进行索引。
    hstack 水平堆叠张量。即第二个维度上增加,等同于 torch.column_stack。
    index_select 在指定的维度上,按索引进行选择数据,然后拼接成新张量。新张量的指定维度上长度是 index 的长度。
    masked_select 根据 mask(0/1, False/True 形式的 mask)索引数据,返回 1-D 张量。
    movedim 移动轴。如 0,1 轴交换:torch.movedim(t, 1, 0) .
    moveaxis 同 movedim。Alias for torch.movedim().(这里发现 pytorch 很多地方会将 dim 和 axis 混用,概念都是一样的。)
    narrow 变窄的张量,从功能看还是索引。在指定轴上,设置起始和长度进行索引。例如:torch.narrow(x, 0, 0, 2), 从第 0 个轴上的第 0 元素开始,索引 2 个元素。x[0:0+2, ...]
    nonzero 返回非零元素的 index。torch.nonzero(torch.tensor([1, 1, 1, 0, 1])) 返回 tensor([[ 0], [ 1], [ 2], [ 4]])。
    permute 交换轴。
    reshape 变换形状。
    row_stack 按行堆叠张量。即第一个维度上增加,等同于 torch.vstack。Alias of torch.vstack().
    scatter_ scatter_(dim, index, src, reduce=None) → Tensor。将 src 中数据根据 index 中的索引按照 dim 的方向填进 input 中。这是一个十分难理解的函数,其中 index 是告诉你哪些位置需要变,src 是告诉你要变的值是什么。
    scatter_add 同 scatter 一样,对 input 进行元素修改,这里是 +=, 而 scatter 是直接替换。
    split 按给定的大小切分出多个张量。例如:torch.split(a, [1, 4]); torch.split(a, 2)
    squeeze 移除张量为 1 的轴。如 t.shape=[1, 3, 224, 224]. t.squeeze().shape -> [3, 224, 224]
    stack 在新的轴上拼接张量。与 hstack\vstack 不同,它是新增一个轴。默认从第 0 个轴插入新轴。
    swapaxes Alias for torch.transpose().交换轴。
    swapdims Alias for torch.transpose().交换轴。
    t 转置。
    take 取张量中的某些元素,返回的是 1D 张量。torch.take(src, torch.tensor([0, 2, 5]))表示取第 0,2,5 个元素。
    take_along_dim 取张量中的某些元素,返回的张量与 index 维度保持一致。可搭配 torch.argmax(t)和 torch.argsort 使用,用于对最大概率所在位置取值,或进行排序,详见官方文档的 example。
    tensor_split 切分张量,核心看 indices_or_sections 变量如何设置。
    tile 将张量重复 X 遍,X 遍表示可按多个维度进行重复。例如:torch.tile(y, (2, 2))
    transpose 交换轴。
    unbind 移除张量的某个轴,并返回一串张量。如[[1], [2], [3]] --> [1], [2], [3] 。把行这个轴拆了。
    unsqueeze 增加一个轴,常用于匹配数据维度。
    vsplit 垂直切分。
    vstack 垂直堆叠。
    where 根据一个是非条件,选择 x 的元素还是 y 的元素,拼接成新张量。
    fill_ 全部填充值。

    Pytorch API 官方文档

  2. scatter_函数讲解:

    '''  
    参数说明:  
    - dim=int,指定操作的维度。  
    - index=LongTensor,指定目标张量中将要被更新的索引。  
    - src=tensor/float,源数据张量,其元素将被放置到目标张量的指定位置。  
    ...  
    '''  
    
    # 以下有值的为默认值  
    torch.Tensor.scatter_(dim, index, src, reduce=None)  
    

    使用示例:

    '''index 则是索引位置的 tensor,而 drc 则是被读取索引位置的 tensor,被更改的是引用方法的实例'''  
    >>> x = torch.rand(2, 5)  
    #tensor([[0.1940, 0.3340, 0.8184, 0.4269, 0.5945],  
    #        [0.2078, 0.5978, 0.0074, 0.0943, 0.0266]])  
    
    >>> torch.zeros(3, 5).scatter_(0, torch.LongTensor([[0, 1, 2, 0, 0], [2, 0, 0, 1, 2]]), x) # index 里的第二个则表示将 src 中的第(1,0)的值填充至被操作 tensor 的(1,2)里  
    # tensor([[0.1940, 0.5978, 0.0074, 0.4269, 0.5945],  
    #         [0.0000, 0.3340, 0.0000, 0.0943, 0.0000],  
    #         [0.2078, 0.0000, 0.8184, 0.0000, 0.0266]])  
    
    >>> src = torch.arange(1, 11).reshape((2, 5))  
    # tensor([[ 1,  2,  3,  4,  5],  
    #         [ 6,  7,  8,  9, 10]])  
    >>> index = torch.tensor([[0, 1, 2, 0]])  
    >>> torch.zeros(3, 5, dtype=src.dtype).scatter_(0, index, src)  
    # tensor([[1, 0, 0, 4, 0],  
    #         [0, 2, 0, 0, 0],  
    #         [0, 0, 3, 0, 0]])  
    
tensor 的随机种子

cuda 和 cpu 上需要分别设置随机种子。

函数 描述
seed() 获取一个随机的随机种子。返回一个 64 位数字,用于初始化随机数生成器(RNG)。
manual_seed(seed) 手动设置随机种子。建议设置为 42,42 能有效地提高模型精度。可以设置为任何数字,只要在实验中保持一致即可。
initial_seed() 返回初始种子。
get_rng_state() 获取随机数生成器状态。返回随机数生成器状态作为一个torch.ByteTensor对象。
set_rng_state(new_state) 设定随机数生成器状态。接受一个torch.ByteTensor对象,该对象包含了随机数生成器的状态信息,并将其设置为当前的 RNG 状态。
tensor 的数学操作
  1. Pointwise Ops,逐元素的操作,如 abs、cos、sin、floor、pow...

  2. Reduction Ops,减少元素的操作,如 argmax、argmin、all、any、mean、norm、var...

  3. Comparison Ops,对比操作,如 ge、gt、le、lt、eq、argsort、isnan、topk...

  4. Spectral Ops,谱操作,如短时傅里叶变换等各类信号处理的函数

  5. Other Operations,其他,如 clone、diag、flip...

  6. BLAS and LAPACK Operations,BLAS(基础线性代数)操作,如 addmm、dot、inner、svd...

自动求导核心——计算图

计算图节点为数据,边为运算,数据基础(输入数据)称为叶子节点。

'''使用示例'''
import torch

w = torch.tensor([1.], requires_grad=True)
x = torch.tensor([2.], requires_grad=True)

a = torch.add(w, x)
b = torch.add(w, 1)     # retain_grad()
y = torch.mul(a, b)

y.backward()
print(w.grad)

# 查看叶子结点
print("is_leaf:\n", w.is_leaf, x.is_leaf, a.is_leaf, b.is_leaf, y.is_leaf)
# 查看梯度
print("gradient:\n", w.grad, x.grad, a.grad, b.grad, y.grad)
# 查看 grad_fn
print("grad_fn:\n", w.grad_fn, x.grad_fn, a.grad_fn, b.grad_fn, y.grad_fn)
# tensor([5.])
# is_leaf: True True False False False
# gradient: tensor([5.]) tensor([2.]) None None None
# grad_fn: None None <AddBackward0 object at 0x7d08b2bc2410> <AddBackward0 object at 0x7d0801326ec0> <MulBackward0 object at 0x7d0801327220>
  1. 非叶子节点在梯度反向传播结束后释放,中间变量的梯度默认不保留,如需保留可以对该节点设置retain_grad()

  2. gard_fn用来记录创建张量时所用的运算,在链式求导法则中会用到。

  3. 在 pytorch 中,自动求导的关键是记录数据和该节点的运算。

  4. 在 tensor 中,数据=data、节点运算=grad_fn。

  5. pytorch 是动态图,TensorFlow 是静态图。

  6. 动态图和静态图的区别:

    • 动态图是在运算的同时搭建计算图,静态图是现搭建计算图再运算。

    • 动态图在运算过程中是计算图可变动的,静态图是运算中计算图不可变。

  7. 动态图和静态图的优缺点:

    • 动态图:

      • 优点:易懂性、灵活性
      • 缺点:性能差
    • 静态图:

      • 优点:部署效率高、性能优化强
      • 缺点:晦涩性、调试困难

Autograd——自动微分

构建出来的计算图是反向的,即在神经网络中 loss 函数计算是根节点,而基础数据是叶子节点。
在根节点调用.backward()函数,即可自动反向传播计算计算图中所有节点的梯度。

  1. 自动求导机制通过有向无环图(DAG)实现。

  2. 在 DAG 中,记录数据(tensor.data)以及操作(tensor.grad_fn)。

  3. 操作在 pytorch 中统称为 function。

  4. 在pytorch中,只有浮点类型和复数类型的张量才能计算梯度,整形和布尔类型不支持梯度计算。

autograd 的使用
  1. 使用频率最高的自动求导函数:torch.autograd.backward

    平常调用的是.backward()函数(使用方法不同),其接口内部调用了 autograd.backward。
    在调用 backward()函数之后,计算图会释放,使得前向传播创建的计算图无法保留,如果需要再次在此计算图上进行操作,则需要调用参数 retain_graph=True,从而保留计算图。

    '''  
    参数说明:  
    - tensors=tensor/tensor sequence,用于求导的张量(一般是损失函数的输出)。  
    - grad_tensors=None/tensor/tensor sequence,雅可比向量积中使用,当传递 grad_tensors 参数时,反向传播会采用传入的 tensor 作为初始梯度而非计算出的结果。  
    - retain_graph=Bool,是否保留计算图,在 pytorch 反向传播结束时,计算图会释放以节省内存。如果需要多次求导,则 retain_graph=True。  
    - creat_graph=Bool,是否创建计算图,用于高阶求导。  
    - inputs=tensor/tensor sequence,用于指定需要计算梯度的输入变量,未指定则默认所有叶节点。  
    - grad_variables,是 grad_tensors 的旧版本,已经被 grad_tensors 替代。  
    '''  
    
    # 以下有值的为默认值  
    torch.autograd.backward(tensors, *, grad_tensors=None,  retain_graph=None, create_graph=False, grad_variables=None, inputs=None)
    
  2. 普通backward函数:

    '''  
    参数说明:  
    - gradient=Tensor/None,一个梯度张量,是反向传播中梯度的权重分布,如果是None,则会默认创建一个与原张量形状相同的全1张量,当调用该函数的张量为矢量时则必须手动指定gradient。    
    - retain_graph=Bool,是否保留计算图,在 pytorch 反向传播结束时,计算图会释放以节省内存。如果需要多次求导,则 retain_graph=True。  
    - creat_graph=Bool,是否创建计算图,用于高阶求导。  
    - inputs=tensor/tensor sequence,允许指定需要计算的梯度的节点输入。  
    '''  
    
    # 以下有值的为默认值
    Tensor.backward(gradient=None, retain_graph=None, create_graph=False, inputs=None)
    
  3. 计算 outputs 对于 inputs 的导数:torch.autograd.grad

    '''  
    参数说明:  
    - outputs=tensor sequence,用于求导的张量(一般是损失函数的输出)。  
    - inputs=tensor sequence/GradientEdge,所要计算导数的张量。  
    - grad_outputs=tensor sequence,雅可比向量积中使用(outputs 梯度的初始值)。  
    - retain_graph=Bool,是否保留计算图,在 pytorch 反向传播结束时,计算图会释放以节省内存。如果需要多次求导,则 retain_graph=True。  
    - creat_graph=Bool,是否创建计算图,用于高阶求导。  
    - inputs=tensor/tensor sequence,用于指定需要计算梯度的输入变量,未指定则默认所有叶节点。  
    - grad_variables,是 grad_tensors 的旧版本,已经被 grad_tensors 替代。  
    '''  
    
    # 以下有值的为默认值  
    torch.autograd.grad(outputs, inputs, *, grad_outputs=None, retain_graph=None, create_graph=False, only_inputs=True, allow_unused=None, is_grads_batched=False, materialize_grads=False)  
    
  4. 自定义操作函数:torch.autograd.Function(待学习)

  5. 梯度不会自动清零,需要通过optimizer.zero_grad()手动清零。

  6. 梯度运算依赖于叶子结点的结点,requiries_grad 默认为 True(依赖于 requires_grad=True 的结点 requires_grad-True)。

  7. 叶子张量不可以执行 in-place 操作(原地替换操作),执行后进行传播运算会报错。

  8. 从计算图中剥离数据可以使用.detach()函数,即以一个新张量的形式返回,并且新张量与旧张量共享数据,可以理解为别名。

    w_detach = w.detach()
    id(w) == id(w_detach)
    
  9. 上下文管理器:with torch.no_grad(),使用with torch.no_grad()可以暂时关闭梯度计算,减少计算开销和内存使用。

pytorch 数据模块

数据的流程:data -> Dataset -> DataLoader -> BatchData -> Model

Dataset

Dataset是一个抽象类,定义如何访问和获取数据集中的样本。

  1. torch.utils.data.Dataset 是一个抽象基类,供用户继承编写自己的 dataset,实现对数据的读取。

  2. Dataset 类的两个必须要实现的函数__getitem____len__

  3. __getitem__函数:实现读取样本的功能,通常传入索引,然后实现从磁盘中读取数据,并进行预处理及数据增强,然后返回一个样本的数据。getitem 返回的数据会在 dataloader 中组装成一个 batch。

  4. __len__函数:返回数据集的大小,如果返回值是 0 则 daraloader 会报错。

  5. torchvision中也有dataset,在torchvision中的dataset是预先处理好的数据集,不需要重写__getitem____len__方法,而util.data中的dataset是一个抽象基类,需要自己实现。

DataLoader

DataLoader是一个迭代器,负责批量加载数据集,并提供数据的迭代功能,接受dataset实例作为输入。

  1. pytorch 数据加载最核心的模块——DataLoader:torch.utils.data.DataLoader

    class torch.utils.data.DataLoader(dataset, batch_size=1, shuffle=None, sampler=None,  
                                      batch_sampler=None, num_workers=0, collate_fn=None, 
                                      pin_memory=False, drop_last=False, timeout=0, 
                                      worker_init_fn=None, multiprocessing_context=None, 
                                      generator=None, *, prefetch_factor=None, 
                                      persistent_workers=False, pin_memory_device='')
    
  2. DataLoader 五大功能:

    • 支持两种形式数据集读取:映射式(Map-style)与迭代式(Iterable-style)。Dataset 类是映射式,gititem 提供了序号到数据的映射,而迭代式是编写一个可迭代对象,从中依次获取数据。

    • 自定义采样策略:可借助 Sampler 自定义采样策略。

    • 自动组装成批数据:自动实现 batch 组装,且可以通过 batch_sampler 和 collate_fn 自定义组装的策略。

    • 多进程数据加载:可以设置 num_workers 改变运算 CPU 核心数。

    • 自动实现锁页内存(Pinning Memory):空间换时间,加快数据的读取数据。

  3. DataLoader 常用API:

    • dataset=Dataset,实现从index/keys到样本的实现,即getitem函数。

    • batch_size=int,单个batch的样本量。

    • shuffle=Bool,每次迭代是否打乱样本顺序,通常训练集需打乱。

    • sampler=Sampler/iterable,设置采样策略,batch_sampler和sampler二选一。

    • batch_sampler=Sampler/iterable,设置采样策略。

    • num_workers=int,设置多少个子进程进行数据加载。

    • collate_fn=Callable,组装数据的规则,决定如何将一批数据组装起来。

    • pin_memory=Bool,是否使用锁页内存。

    • drop_last=Bool,每个epoch是否放弃最后一批不足batch size大小的数据,一般如果数据量足够多,通常会设置为True,这样会使得模型训练更加稳定。

  4. DataLoader的原理:首先将dataset返回的数据解包后zip(将相同数据放在一起),然后对实际的数据进行组装并读取加载batch。

  5. DataLoader参数解析:

    • 如果sampler 和 batch_sampler 都为 None,batch_sampler 将使用内置的 BatchSampler,sampler 有两种情况:
      • 若 shuffle=True,则 sampler=RandomSampler(dataset)
      • 若 shuffle=False,则 sampler=SequentialSampler(dataset)
    • 如果使用自定义 batch_sampler
      • 不能指定 sampler , batch_size, shuffle, drop_last
      • sampler 作为 batch_sampler的参数传入
    • 如果使用自定义 sampler,则不能指定 shuffle,这是因为 shuffle 仅用于缺省 sampler 时选择 pytorch 内置的 sampler
    • 如果 dataset 使用 IterableDataset,则不能指定 sampler, batch_sampler
      • sampler 将使用 InfiniteConstantSampler(),这是一个 dummy sampler,调用 _iter(self) 方法永远返回 None
      • 可以指定 batch_size, drop_last,batch_sampler 将使用内置的 BatchSampler。
        摘自:Pytorch 源码解读

dataset及常用API

Dataset和split
  1. ConcatDataset:

    def __getitem__(self, idx):  
        if idx < 0:  
            if -idx > len(self):  
                raise ValueError("absolute value of index should not exceed dataset length")  
            idx = len(self) + idx  
        dataset_idx = bisect.bisect_right(self.cumulative_sizes, idx)
        if dataset_idx == 0:  
            sample_idx = idx  
        else:  
            sample_idx = idx - self.cumulative_sizes[dataset_idx - 1]  
    
        return self.datasets[dataset_idx][sample_idx]  
    
  2. Subset:根据指定的索引获取子数据集。

    def __init(self, dataset:Dataset[T_co], indices:Sequence[int]) -> None:
        self.dataset = dataset
        self.indices = indices
    
    def __getitem__(self, idx):
        if isinstance(idx, list):
            return self.dataset[[self.indices[i] for i in idx]]
        return self.dataset[self.indices[idx]]
    
    def __len__(self):
        return len(self.indices)
    
  3. random_split:随机的将dataset划分为多个不重叠的子集,适合用来划分训练、验证集,但其划分不可见,不利于分析。

    # 以下有值的为默认值  
    torch.utils.data.random_split(dataset, lengths, generator=<torch._C.Generator object>)
    
    '''
    参数说明:  
    - dataset=Dataset,需要划分的数据集。  
    - lengths=int list/float list,要划分的长度或者分数。  
    - generator=Generator,随机的generator。  
    '''
    
Sampler
  1. sampler和batch_sampler的关系:

    • auto_collation时采用的是batch_sampler。

    • sampler是数据集中单个样本的处理,返回的是单个样本的索引,而batch_smapler是一个batch的处理,返回的是一个batch的索引list。

    • 通常是用batch_sampler,对应的是BatchSampler类。

  2. BatchSampler:

    # 以下有值的为默认值  
    torch.utils.data.BatchSampler(sampler, batch_size, drop_last)
    
    '''
    参数说明:
    - sampler=sampler/iterable,采样器,一般需要shuffle时传入RandomSampler,如果不需要打乱,SequentialSampler。  
    - drop_last=Bool,是否将使用不足一个batch的数据。  
    '''  
    
  3. batch_sampler就在这RandomSampler和SequentialSampler上封装了一个批抽取功能。

  4. BatchSampler的部分实现:

    def __iter__(self) -> Iterator[list[int]]:
        batch = []
        for idx in self.sampler:
            batch.append(idx)
            if len(batch) == self.batch_size:
                yield batch
                batch = []
    # drop_last部分的实现
        if len(batch) > 0 and not self.drop_last:
            yield batch
    
  5. SequentialSampler部分实现:顺序迭代器

    def __iter__(self) -> Iterator[int]:
        return iter(range(len(self.data_source)))
    
  6. RandomSampler部分实现:随机迭代器(随机策略由generator实现,generator默认采用随机种子进行设置)

    def __iter__(self) -> Iterator[int]:
        n = len(self.data_source)
        if self.generator is None:
            seed = int(torch.empty((), dtype=torch.int64).random_().item())
        else:
            generator = self.generator
    
        if self.replacement:
            for _ in range(self.num_samples // 32):
                yield from torch.randint(high=n, size=(32,), dtype=torch.int64, generator=generator).tolist()
            yield from torch.randint(high=n, size=(self.num_samples % 32,), dtype=torch.int64, generator=generator).tolist()
        else:
            yield from torch.randperm(n, generator=generator).tolist
    
  7. SubsetRandomSampler:索引定义子集(采样数即索引lsit长度)随机采样器

    def __iter__(self) -> Iterator[int]:
        for i in torch.randperm(len(self.indices), generator=self.generator):
            yield self.indices[i]
    
  8. Sampler本质上是迭代器,用于产生数据集的索引值序列;而BatchSampler是将Sampler采样得到的索引值进行合并,从而获得Batch索引。

WeightedRandomSampler
  1. WeightedRandomSampler函数目的是加权采样从而实现数据均衡采样。

  2. WeighterRandomSamlpler函数:

    '''
    参数说明:  
    - weights=float sequence,每个样本的采样权重,权重总和不必为1,只需关注样本之间的比例即可。  
    - num_samples=int,采样数量,一般设为样本总量。  
    - replacement=Bool,是否有放回采样,True表示有放回。  
    - generator=Generator,自定义生成器,通常是默认的。  
    '''  
    
    # 以下有值的为默认值  
    torch.utils.data.WeightedRandomSampler(weights, num_samples, replacement=True, generator=None)  
    
  3. sampler构建的步骤:

    • 计算各类的采样概率。

    • 生成每个样本的概率(权重)。

    • 实例化WeightedRandomSampler。

transforms

transforms是pytorch的数据增强函数库。

  1. 数据增强分为在线(online)和离线(offline)两种方式,离线是指在训练开始前对数据进行变换,在线则是在训练过程中每次加载数据都对数据进行变换。pytorch的transforms是在线方式。

  2. 运行机制:采用transforms.Compose把变换方法包装起来,放入dataset,在dataloader依次读数据时,调用dataset的getitem,使得读取每个sample时,都会根据compose中的方法依次对数据进行变换,完成数据增强。

变换方法
  1. v2.Compose(list):组合多个变换。

    torchvision.transforms.v2.Compose(transforms: Sequence[Callable])
    
  2. v2.Resize:调整图像大小。

    '''
    参数说明:
    - size=sequence/int,图像调整目标大小,如果是int,则会自动调整输出尺寸。  
    - interpolation=interpolationMode,用于调整大小的插值方法。  
    - max_size=int,限制调整后图像的最大尺寸,如果size是个比例,则会限制图像的最大高度或宽度。  
    - antialias=Bool,是否使用抗锯齿技术。  
    '''
    
    # 以下有值的为默认值  
    torchvision.transforms.v2.Resize(size, interpolation=InterpolationMode.BILINEAR, max_size=None, antialias=True)
    
  3. ToTensor:将PIL对象或nd.array对象转换为tensor,并且对数值缩放到[0, 1]之间,并且对通道进行右移。

    from PIL import Image
    
    image = Image.open('xxx')
    
    # 创建一个ToTensor变换
    to_tensor_transform = torchvision.transforms.ToTensor()
    
    # 应用ToTensor变换
    tensor_image = to_tensor_transform(image)
    
  4. Normalize:对tensor对象进行逐通道标准化(减均值再除以标准差)。

    '''
    参数说明:
    - mean=sequence,通常是通过计算训练数据集中所有图像每个通道的均值得到的。  
    - std=sequence,通常是通过计算训练数据集中所有图像每个通道的方差得到的,并取其平方根。  
    - inplace=Bool,指示是否直接在原始张量上进行变换,在compose中一般是False。   
    '''
    
    # 以下有值的为默认值  
    torchvision.transforms.v2.Normalize(mean, std, inplace=False)
    
  5. FiveCrop&TenCrop:神器。

自动数据增强
  1. AutoAugmentPolicy:AutoAugment的自动数据增强策略,其包含多个针对特定数据集的策略,例如:针对ImageNet数据集的AutoAugmentPolicy.IMAGENET。

  2. v2.AutoAugment:自动数据增强方法。

    '''
    参数说明:
    - policy=AutoAugemntPolicy,数据增强策略。  
    - interpolation=InterpolationMode,设置插值方法。  
    - fill=sequeence/number,设置填充像素的像素值,默认为0,黑色。  
    '''  
    
    # 以下有值的为默认值  
    torchvision.transforms.v2.AutoAugment(policy=AutoAugmentPolicy.IMAGENET, interpolation=InterpolationMode.NEAREST, fill=None)
    
  3. 一个数据增强策略AutoAugmentPolicy内包含很多组变换,实际应用中是固定一种变换策略,这一组变换策略是由强化学习自动搜索的,所以是自动数据增强策略。

  4. v2.RandAugment:随机策略增强方法(不需要搜索最优策略,计算成本低,实现简单)。

    '''
    参数说明:
    - num_ops=int,执行变换次数。  
    - magnitude=int,每个变换的强度。  
    - num_magnitude_bins=int,增强强度分桶的数量,在应用增强时,会从bins中随机选择一个作为实际应用的增强强度。  
    - interpolation=InterpolationMode,设置插值方法。  
    - fill=sequeence/number,设置填充像素的像素值,默认为0,黑色。  
    '''
    
    # 以下有值的为默认值  
    torchvision.transforms.v2.RandAugment(num_ops=2, magnitude=9, num_magnitude_bins=31, interpolation=InterpolationMode.NEAREST, fill=None)
    
  5. v2.TrivialAugmentWide:采用NAS技术搜索策略的数据增强方法。

    '''
    参数说明:
    - num_magnitude_bins=int,增强强度分桶的数量,在应用增强时,会从bins中随机选择一个作为实际应用的增强强度。  
    - interpolation=InterpolationMode,设置插值方法。  
    - fill=sequeence/number,设置填充像素的像素值,默认为0:黑色。  
    '''
    
    # 以下有值的为默认值  
    torchvision.transforms.v2.TrivialAugmentWide(num_magnitude_bins=31, interpolation=InterpolationMode.NEAREST, fill=None)
    
  6. 数据增强应该被视为数据变换过程的一部分,在数据被加载到模型之前应用(即先对图作处理,然后再对将其转换为tensor)。这样可以确保模型在训练过程中能够接触到各种变换后的数据,从而提高其泛化能力。

  7. 三种自动数据增强方法比较:

    • AutoAugment 是一种自动搜索最佳数据增强组合的方法,可以找到最优的增强策略,但需要大量计算资源。
    • RandAugment 是 AutoAugment 的一个变体,通过固定增强操作的数量和强度来简化搜索过程,计算资源需求较小。
    • TrivialAugment 是一种非常简单的数据增强策略,只包含两种增强操作,易于实现和理解,但可能不如其他策略有效。

torchvision 经典dataset学习

  1. X-MNIST:除了FashionMNIST及KMNIST需要修改url和classes,其他函数均可复用MNIST中功能。

    # __getitem__的实现:
    def __getitem__(self, index:int) -> Tuple[Any, Any]:
        img, target = self.data[index], int(self.targets[index])
        img = Image.fromarray(img.numpy(), mode='L')
    
        if self.transform is not None:
            img = self.transform(img)
        if self.target_transform is not None:
            target = self.target_transform(target)
        return img, target
    
  2. cifar-10:

    # __getitem__的实现:
    def __getitem__(self, index:int) -> Tuple[Any, Any]:
        img, target = self.data[index], self.targets[index]
        img = Image.fromarray(img)
    
        if self.transform is not None:
            img = self.transform(img)
        if self.target_transform is not None:
            target = self.target_transform(target)
    
        return img, target
    
  3. self.dataself.target的获取实现:

    for file_name, checksum in downloaded_list:
        file_path = os.path.join(self.root, self.base_folder, file_name)
        with open(file_path, 'rb') as f:
            entry = pickle.load(f, encoding='latin1')
            self.data.append(entry['data'])
            if 'labels' in entry:
                self.targets.extend(entry['label'])
            else:
                self.targets.extend(entry['fine_labels'])
    
    self.data = np.vstack(self.data).reshape(-1, 3, 32, 32)
    self.data = self.data.transpose((0, 2, 3, 1))  # convert to HWC
    '''
    参数说明:
    
  4. VOC:复杂的目标检测数据集。

    def __getitem__(self, index:int) -> Tuple[Any, Any]:
        img = Image.open(slef.images[index]).convert('RGB')
        target = self.parse_voc_xml(ET_parse(self.annotations[index]).getroot())
    
        if self.transforms is not None:
            img, target = self.transforms(img, target)
    
        return img, target
    
  5. COCO:大规模视觉数据集,主要用于目标检测,体量远大于VOC。

    def __getitem__(self, index:int) -> Tuple[Any, Any]:
        id = self.ids[index]
        image = self._load_image(id)
        target = self._load_image(id)
    
        if self.transforms is not None:
            image, target = self.transforms(image, target)
    
        return image, target
    

pytorch 模型模块

Module & Parameter

Module(module)

调用继承于Mdule的model实例时会自动调用forward函数。

  1. 深度神经网络就是常说的模型(Model),一个模型包含很多网络层,多个网络层构建成一个模型。在pytorch中模型是一个Module,各网路层、模块也是module(一般Module指模型或者基类,而module指模块)。

  2. Module是所有神经网络的基类,所有模型都必须继承与Module类,并且可以嵌套(一个module可以包含多个子module)

  3. Module定义的属性(八大核心属性)来管理模块功能

    self._modules = OrderedDict()
    self._parameters = OrderedDict()
    self._buffers = OrderedDict()
    self._backward_hooks = OrderedDict()
    self._forward_hooks = OrderedDict()
    self._forward_pre_hooks = OrderedDict()
    self._state_dict_hooks = OrderedDict()
    self._load_state_dict_pre_hooks = OrderedDict()
    
    '''
    _modules : 存储管理nn.Module类
    _parameters: 存储管理nn.Parameter类
    _buffers:存储管理缓冲属性,如BN层中的running_mean
    *_hooks:存储管理钩子函数
    '''  
    
  4. forward函数:forward之于Module等价于__getitem__之于Dataset,forward函数是模型每次调用的具体实现,所有的模型(module)必须实现forward函数(否则调用时会报错)。

  5. 模型的创建:

    • 构建子模块:构建网络所需要的网络层,如卷积层、池化层、全连接层等。

    • 拼接子模块:在forward函数中定义需要执行的功能,即将子模块以一定的方式拼接起来,完成对数据的向前传播。

    实践化的操作:

    • 写一个继承于Module的类。

    • 在init函数中把需要的网络层创建好。

    • 在forward函数中把模型搭建的规则完成。

Parameter
  1. Module中的Parameter是指可学习的模型的参数,如卷积层的卷积核权重和偏置、Linear层的权重和偏置...

  2. Module中的parameters()方法会返回创建的model的parameter。

Module容器 —— Containers

Sequential
  1. Sequential的作用是将一系列网络层按固定的先后顺序串起来(一个整体),使得调用时数据从第一个层按顺序执行到最后一个层。

  2. Sequential中既可以直接传入module,也可以传入OrderDict(使得容器中的每个module都有名字)。

    # 官方示例  
    model = nn.Sequential(
            nn.Conv2d(1,20,5),
            nn.ReLU(),
            nn.Conv2d(20,64,5),
            nn.ReLU()
            )
    
    model = nn.Sequential(OrderedDict([
            ('conv1', nn.Conv2d(1,20,5)),
            ('relu1', nn.ReLU()),
            ('conv2', nn.Conv2d(20,64,5)),
            ('relu2', nn.ReLU())
            ]))
    
  3. sequential也是一个module,所以在调用时也会进入_call_impl_调用其forward函数。

    # sequence的forward函数:  
    def forward(self, input):
        for module in self:
            input = module(input)
        return input
    
    # 由此可得sequential类的功能  
    
ModuleList、ModuleDict和ParameterList、Parameterict
  1. ModuleList实际上是对list的一个封装,以实现list中的内容可以封入_modules属性下,同时封装list后的网络在调用时需要迭代使用或取出。

    class MyModule(nn.Module):
        def __init__(self):
            super(MyModule, self).__init__()
            self.linears = nn.ModuleList([nn.Linear(10, 10) for i in range(10)])
            # self.linears = [nn.Linear(10, 10) for i in range(10)]    
    
        def forward(self, x):
            for sub_layer in self.linears:
                x = sub_layer(x)
            return x
    
  2. ModuleDict是对OrderDict的一个封装,以实现OrderDict中的内容可以封入_modules的属性下。

    class MyModule2(nn.Module):
        def __init__(self):
            super(MyModule2, self).__init__()
            self.choices = nn.ModuleDict({
                    'conv': nn.Conv2d(3, 16, 5),
                    'pool': nn.MaxPool2d(3)
            })
            self.activations = nn.ModuleDict({
                    'lrelu': nn.LeakyReLU(),
                    'prelu': nn.PReLU()
            })
    
        def forward(self, x, choice, act):
            x = self.choices[choice](x)
            x = self.activations[act](x)
            return x
    
    
  3. Parameter和ParameterDict与Module中的ModuleList和ModuleDict类似,使用方法也相同。

    # ParaemterDict
    class MyModule(nn.Module):
        def __init__(self):
            super(MyModule, self).__init__()
            self.params = nn.ParameterDict({
                    'left': nn.Parameter(torch.randn(5, 10)),
                    'right': nn.Parameter(torch.randn(5, 10))
            })
    
        def forward(self, x, choice):
            x = self.params[choice].mm(x)
            return x
    
    # ParaemterList
    class MyModule(nn.Module):
        def __init__(self):
            super(MyModule, self).__init__()
            self.params = nn.ParameterList([nn.Parameter(torch.randn(10, 10)) for i in range(10)])
    
        def forward(self, x):
            # ParameterList can act as an iterable, or be indexed using ints
            for i, p in enumerate(self.params):
                x = self.params[i // 2].mm(x) + p.mm(x)
            return x
    

常用网络层

  1. Convolutional Layers:卷积层。

    '''
    参数说明:
    - in_channels=int,输入特征图的通道数。  
    - out_channels=int,输出特征图通道数,等价于卷积核数量。  
    - kernel_size=sequence/number,卷积核大小。  
    - stride=int/tuple,卷积核卷积步长。  
    - padding=int/tuple/mode,填充像素数量或填充模式。  
    - padding_mode='zeros'/'reflect'/'replicate'/'circular',填充模式。  
    - dilation=int/tuple,孔洞卷积的孔洞大小。  
    - groups=int,分组卷积的分组。  
    - bias=Bool,是否采用偏置。  
    ...
    '''
    
    # 以下有值的为默认值  
    torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros', device=None, dtype=None)
    
    # input/output=(N, C, H, W)
    
  2. Pooling Layers:池化层。

    有四种:最大值池化Maxpool、平均值池化Avgpool、分数阶池化FractionalMaxPool、基于范数的池化LPPool

    # 以Maxpool为例:  
    '''
    参数说明:
    - kernel_size=int/int tuple,池化窗口大小。  
    - stride=int/int tuple,滑动步长,默认则stride=kernel_size。  
    - padding=int/int tuple,原图填充大小。   
    - dilation=int/int tuple,孔洞大小。  
    - return_indices=Bool,是否返回最大值所在位置,主要在torch.nn.MaxUnpool2d中使用。  
    - ceil_mode=Bool,当无法整除时,是向下取整还是向上取正,默认向下。  
    '''
    
    # 以下有值的为默认值  
    torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
    
  3. Adaptive Pooling Layers:自适应池化层(保证输出的图像尺寸固定)。

    torch.nn.AdaptiveMaxPool2d(output_size, return_indices=False)  
    
  4. Padding Layers:调整特征图分辨率(给特征图周围填充一定像素)。

    '''padding=int/tuple,int等边或tuple(left, right, top, bottom)'''
    # 镜像填充  
    torch.nn.ReflectionPad2d(padding)
    
    # 边界重复填充  
    torch.nn.ReplicationPad2d(padding)  
    
    # 指定值填充  
    torch.nn.ConstantPad2d(padding, value)
    
    # 零值填充  
    torch.nn.ZeroPad2d(padding)
    
  5. Linear Layers:全连接层。

    • Identity:恒等变换(不对输出作任何变换,通常用于占位)。

      identity = torch.nn.Identity()
      
    • Linear:全连接层。

      '''
      参数说明:
      - in_features=int,输入特征的数量(tensor的列)。  
      - out_features=int,输出特征的数量。  
      - bias=Bool,是否添加偏置项。   
      ...
      '''
      
      # 以下有值的为默认值  
      torch.nn.Linear(in_features, out_features, bias=True, device=None, dtype=None)
      
    • Bilinear:双线性层。

      '''
      参数说明:
      - in1_features=int,第一个输入特征的数量(tensor的列)。  
      - in2_features=int,第二个输入特征的数量(tensor的列)。  
      - out_features=int,输出特征的数量。  
      - bias=Bool,是否添加偏置项。   
      ...
      '''
      
      # 以下有值的为默认值 
      torch.nn.Bilinear(in1_features, in2_features, out_features, bias=True, device=None, dtype=None)
      
    • LazyLinear:Lazy版本的Linear,可自动设定in_featurs参数。

      torch.nn.LazyLinear(out_features, bias=True, device=None, dtype=None)
      
  6. Normaliation Layers:标准化层,分别有BN、LN、IN、GN及早期LRN。

    • BN

      '''
      参数说明:
      - num_features=int,输入的通道数,决定BN层有多少个γ和β。  
      - eps=float,防止分母为0。  
      - momentum=float,用于计算这些运行统计量的指数移动平均值,控制运行平均值和运行方差的更新速度。  
      - affine=Bool,是否执行乘以γ、加β的操作。   
      - track_running_stats=Bool,是否需要执行记录统计均值、统计方差。  
      ...
      '''
      
      # 以下有值的为默认值 
      torch.nn.BatchNorm2d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, device=None, dtype=None)
      
    • GN:针对小batch size,统计的均值方差存在较大差异,提出分组进行统计。
      '''
      参数说明:

    • LN:针对RNN这类序列网络设计,以层为单位进行统计均值、方差。

    • IN:针对风格迁移这类GAN任务,不同风格实例差异较大,以实例为单位进行统计均值和方差。

    • LRN:现在很少采用。

  7. Dropout Layers:

    '''
    参数说明:
    - p=float,dropout 比率,即被置零的激活单元的比例。  
    - inplace=Bool,是否直接修改输入张量而不是创建一个新的输出张量。  
    '''
    
    # 以下有值的为默认值 
    torch.nn.Dropout(p=0.5, inplace=False)
    
  8. Alpha Dropout:保持输入均值和方差不变的Dropout

    '''
    参数说明:
    - p=float,dropout 比率,即被置零的激活单元的比例。  
    - inplace=Bool,是否直接修改输入张量而不是创建一个新的输出张量。  
    '''
    
    # 以下有值的为默认值 
    torch.nn.AlphaDropout(p=0.5, inplace=False)
    
  9. Non-linear Layers:非线性激活函数层

    • 应用于每个神经元的加权输入总和上的普通非线性激活函数。

      • ReLU:

        torch.nn.ReLU(inplace=False)
        
      • Sigmoid:

        torch.nn.Sigmoid()
        
      • Tanh:

        torch.nn.Tanh()
        
    • 用于特定的网络结构或特定的任务,对输出神经元进行Softmax的、变为概率分布形式的的特殊非线性激活函数。

      • Softmax:

        # dim=int,是应用softmax函数的维度,默认是最后一维,用在batch的情况下(对于一个形状为 [batch_size, num_classes] 的张量,设置 dim=1,在每个样本的所有类别得分上应用 Softmax,保留不同样本之间的得分独立性)。
        torch.nn.Softmax(dim=None)  
        
      • AdaptiveLogSoftmaxWithLoss:适用于大数据量

        '''
        参数说明:
        - in_features=int,输入特征的数量,即自适应 Softmax 层的输入大小。  
        - n_classes=int,总类别数,包括所有子集类别。  
        - cutoffs=Sequence,一个列表,指定了每个组中类别的数量(作为bins的edge存在)。  
        - div_value=float,计算每个组的特征维度的大小的除数。  
        - head_bias=Bool,是否在输出层的每个头之前添加一个可学习的偏差。  
        '''
        
        # 以下有值的为默认值 
        torch.nn.AdaptiveLogSoftmaxWithLoss(in_features, n_classes, cutoffs, div_value=4.0, head_bias=False, device=None, dtype=None)  
        
  10. 将多维展开的函数(将Conv层输出展开使得linear可以接收):view()

    Tensor.view(*shape)
    # 一般常用 x = x.view(x.size(0), -1)
    

Module常用API函数

  1. 设置模型训练、评估模式:

    • .eval():设置模型为评估模式。

    • .train(mode=True):设置模型为训练模式。

  2. 设置模型存放在CPU/GPU:

    • .cpu().to("cpu"):将module放到CPU上。

    • .cuda().to("cuda"):将module放到cuda上。

  3. 获取模型参数,加载权重参数:

    • .state_dict():获取模型参数,返回参数字典dict[layes: parameters]。

      '''  
      参数说明:
      - prefix=str,字典中每个参数键的前面的前缀。  
      - keep_vars=Bool,是否与模型中的参数保持链接。  
      '''  
      
      # 以下有值的为默认值 
      .state_dict(*, prefix='', keep_vars=False)
      
    • .load_state_dict():加载参数,将参数字典中的参数复制到当前模型中,key需要一一对应。

      '''  
      参数说明:
      - state_dict=dict,要加载的参数的字典。  
      - strict=Bool,加载的状态字典是否必须完全匹配模型中的参数键,如果是False则可以省略不匹配的键,而不是报错。  
      - assign=Bool,是否直接将字典中的参数赋给Module,如果True,则加载后模块的参数和状态字典中的参数将共享相同的内存空间。  
      '''  
      
      # 以下有值的为默认值  
      .load_state_dict(state_dict, strict=True, assign=False)
      
  4. 管理模型的modules、parameters、sub_module:

    • .parameters(recurse=True):(recurse:是否包含子模块的参数)返回一个可抛出Module的所有parameter对象的迭代器。

    • .named_parameters(prefix='', recurse=True, remove_duplicate=True):作用同上,还可获得其名称。

    • .modules():返回一个迭代器,迭代器可以抛出Module的所有Module对象(也会获得模型自己)。

    • .named_modules(memo=None, prefix='', remove_duplicate=True):(memo=dict,用于存储已经访问过的模块,避免重复访问)作用同上,还可获得其名称。

    • .children():作用同modules,但不会返回Module自己。

    • .named_children():作用同named_modules,但不会返回Module自己。

  5. 获取某个参数或submodule:

    • .get_parameter(target):(target是参数完整的路径)获得target的参数。

    • .get_submodule(target):(target是参数完整的路径)获取target模块实例。

  6. 设置模型的参数精度:

    • .half():半精度16。

    • .float():单精度32。

    • .double():双精度64。

    • .bfloat16():特殊的半精度16,性能比普通float16更强。

  7. 对子模块执行特定功能:

    • .zero_grad(set_to_none=True):将模型的梯度设置为0或None。

    • .apply(fn):递归地应用函数fn到模块及其所有子模块上。

Hook函数及Grad-CAM

hook函数

hook相当于插件,可以实现一些额外的功能,而又不用修改主体代码。把这些额外功能实现了挂在主代码上,所以叫钩子。

  1. hook的出现与pytorch的计算机制有关,pytorch会在运算结束后将中间变量释放,当我们需要中间变量时,又不能修改模型主体代码时,hook便可实现。

  2. Tensor.register_hook:注册一个反向传播的hook函数(特殊的回调函数),函数在计算tensor的梯度时(即反向传播时)自动执行,hook会影响 依赖调用hook函数的梯度 的梯度。

    '''
    hook=python method,是需要的函数。
    这个函数在Tensor上被应用,接受的method类型是hook(grad)。  
    返回一个torch.utils.hooks.RemovableHandle对象(可通过调用remove方法移除)。  
    '''  
    torch.Tensor.register_hook(hook)   
    
    # 使用示例:  
    def grad_hook(grad):
        grad *= 2
    x = torch.tensor([2., 2., 2., 2.], requires_grad=True)
    y = torch.pow(x, 2)
    z = torch.mean(y)
    h = x.register_hook(grad_hook)
    z.backward()
    print(x.grad)
    h.remove()    # removes the hook
    
    >>> tensor([2., 2., 2., 2.])
    
  3. Module.register_forward_hook:注册前向传播的hook,在前向传播后自动调用hook函数。

    '''
    hook=python method,是需要的函数。
    接受的method的类型是hook(module, input, output)。  
    返回一个torch.utils.hooks.RemovableHandle对象(可通过调用remove方法移除)。  
    '''
    torch.nn.Module.register_forward_hook(hook)
    
    # 使用示例:  
    class MyModule(nn.Module):
        def forward(self, input):
            return input + 1
    
    # 实例化模块
    module = MyModule()
    
    # 定义一个钩子函数
    def forward_hook(module, input, output):
        print(f"Module {module} received input {input} and returned output {output}")
    
    # 注册钩子
    module.register_forward_hook(forward_hook)
    
    # 创建一个输入张量
    input_tensor = torch.tensor(1.0)
    
    # 调用模块的前向传播
    output = module(input_tensor)
    
    >>> Module MyModule() received input (tensor(1.),) and returned output 2.0
    
  4. register_forward_pre_hook:注册在执行forward之前调用的hook函数。

    '''
    hook=python method,是需要的函数。
    接受的method的类型是hook(module, input)。  
    返回一个torch.utils.hooks.RemovableHandle对象(可通过调用remove方法移除)。  
    '''
    torch.nn.Module.register_forward_pre_hook(hook)
    
  5. register_full_backward_hook:注册反向传播中的hook函数,在每次计算module的梯度后自动调用hook函数。

    '''
    hook=python method,是需要的函数。
    接受的method的类型是hook(module, grad_input, grad_output)。  
    返回一个torch.utils.hooks.RemovableHandle对象(可通过调用remove方法移除)。  
    当module有多个输入或输出时,grad_input和grad_output是一个tuple。  
    '''
    torch.nn.Module.register_full_backward_hook(hook)
    
Grad-CAM
  1. 原理:
    register_full_backward_hook实现特征图的提取,并结合Grad-CAM方法对卷积神经网络的学习模式进行可视化。

  2. 实现方法:

    1. 创建网络net;
    2. 注册forward_hook函数用于提取最后一层特征图;
    3. 注册backward_hook函数用于提取类向量(one-hot)关于特征图的梯度;
    4. 对特征图的梯度进行求均值,并对特征图进行加权;
    5. 可视化heatmap。

经典Model代码分析

  1. AlexNet:

    • 代码中定义了一个AlexNet类与一个alexnet函数,这样的封装形式贯穿整个torchvision的模型定义。

    • AlexNet类是nn.Module,其中定义了AlexNet模型的具体结构,而alexnet函数则是对Alexnet类的包装,并且实现加载预训练参数的功能。

      # alexnet函数的实现
      model = AlexNet(**kwargs)
      if pretrained:
          state_dict = load_state_dict_from_url(model_urls["alexnet"], progress=progress)
          model.load_state_dict(state_dict)
      
      return model
      
    • Linear层之前可通过torch.flatten将数据变为一维向量。

  2. VGG:

    • VGG中包含的主要类和函数:

      • VGG类是一个nn.module。

      • _vgg函数:vgg函数接收具体的网络参数,以此决定返回哪一个vgg模型;

      • vggxxx:定义了具体VGG所需要的参数,并调用_vgg函数得到具体模型;

      • make_layers函数:创建可抽象出来、共性的网络层函数,即网络结构图中的5次堆叠部分。

      • cfgs字典:配置各具体vgg模型所需要的参数,主要在make_layers中使用。

    • 调用关系与逻辑:

      vgg16() --> _vgg() --> make_layers --> VGG

  3. GoogLeNet:

    反复使用的模块抽象为一个Moudle类,并作为参数进行调用。

  4. Resnet:

    resnet的搭建是将block抽象出来提供接口,由用户自行传入,并且设定堆叠次数。

权重初始化方法

Xavier系列和Kaiming系列
  1. xavier_uniform_:均匀分布;

    '''
    - tensor=tensor,要初始化的张量。  
    - gain=float,放大因子或增益因子,可以根据激活函数类型进行调整(建议使用gain=torch.nn.init.calculate_gain('xxxx'))。  
    - genetator=generator,随机数生成器。  
    返回值是None,原地替换,以下相同。
    '''
    
    # 以下有值的为默认值  
    torch.nn.init.xavier_uniform_(tensor, gain=1.0, generator=None)
    

    \(U(-a, a)\)
    \(a=gain*\sqrt{\frac{6}{fan_in+fan_out}}\)

  2. xavier_normal_:正态分布;

    '''
    - tensor=tensor,要初始化的张量。  
    - gain=float,放大因子或增益因子,可以根据激活函数类型进行调整(建议使用gain=torch.nn.init.calculate_gain('xxxx'))。  
    - genetator=generator,随机数生成器。  
    '''
    
    # 以下有值的为默认值  
    torch.nn.init.xavier_normal_(tensor, gain=1.0, generator=None)
    
  3. kaiming_uniform_:均匀分布;

    '''
    - tensor=tensor,要初始化的张量。  
    - a=float,leaky ReLU激活函数的负斜率系数。  
    - mode='fan_in'/'fan_out',计算方差的模式,'fan_in'在正向传播中保持权重要素的方差大小,通常,后接ReLU激活的层用'fan_in',后接sigmoid或tanh激活的层用'fan_out'。  
    - nonlinearity='relu'/'leaky_relu',该层之后使用的非线性函数。  
    '''
    
    # 以下有值的为默认值 
    torch.nn.init.kaiming_uniform_(tensor, a=0, mode='fan_in', nonlinearity='leaky_relu', generator=None)  
    
  4. kaiming_normal_:正态分布;

    '''
    - tensor=tensor,要初始化的张量。  
    - a=float,leaky ReLU激活函数的负斜率系数。  
    - mode='fan_in'/'fan_out',计算方差的模式,'fan_in'在正向传播中保持权重要素的方差大小,通常,后接ReLU激活的层用'fan_in',后接sigmoid或tanh激活的层用'fan_out'。  
    - nonlinearity='relu'/'leaky_relu',该层之后使用的非线性函数。  
    '''
    
    # 以下有值的为默认值  
    torch.nn.init.kaiming_normal_(tensor, a=0, mode='fan_in', nonlinearity='leaky_relu', generator=None)  
    
其他方法
  1. 使值均匀分布\(U(a, b)\)初始化:torch.nn.init.uniform_(tensor,a=0,b=1)

  2. 使值正态分布\(N(mean, std)\)初始化:torch.nn.init.normal_(tensor, mean=0, std=1)

  3. 使值常数val初始化:torch.nn.init.constant_(tensor, val)

  4. 使值单位矩阵初始化:torch.nn.init.eye_(tensor)将二维tensor初始化为单位矩阵

  5. 使值正交初始化:torch.nn.init.orthogonal_(tensor, gain=1)

  6. 使值从正态分布\(N(0, std)\)稀疏(使每个column有一部分为0,sparsity是稀疏/0的比例)初始化:torch.nn.init.sparse_(tensor, sparsity, std=0.01)

  7. 使值全0初始化:torch.nn.init.zeros_(tensor)

  8. 使值全1初始化:torch.nn.init.ones_(tensor)

  9. 狄拉克初始化:torch.nn.init.dirac_(tensor, groups=1)

  10. 增益计算:torch.nn.init.calculate_gain(nonlinearity, param=None)

nonlinearity gain
Linear / Identity 1
Conv{1,2,3}D 1
Sigmoid 1
Tanh 35
ReLU 2
Leaky Relu 1+negative_slope22
SELU 43

pytorch 优化模块

损失函数

nn.L1Loss
nn.MSELoss
nn.CrossEntropyLoss
nn.CTCLoss
nn.NLLLoss
nn.PoissonNLLLoss
nn.GaussianNLLLoss
nn.KLDivLoss
nn.BCELoss
nn.BCEWithLogitsLoss
nn.MarginRankingLoss
nn.HingeEmbeddingLoss
nn.MultiLabelMarginLoss
nn.HuberLoss
nn.SmoothL1Loss
nn.SoftMarginLoss
nn.MultiLabelSoftMarginLoss
nn.CosineEmbeddingLoss
nn.MultiMarginLoss
nn.TripletMarginLoss
nn.TripletMarginWithDistanceLoss
  1. L1loss:

    '''
    - reduction='mean'/'sum'/'none',是否需要对loss进行降维,即对batch的损失函数的处理,none是无处理,mean是输出平均值,sum是输出和。  
    '''
    
    # 以下有值的为默认值  
    torch.nn.L1Loss(reductio='mean')  
    
  2. CrossEntropyLoss:会自动调用softmax函数,所以不用手动接softmax函数

    '''  
    - weight=tensor/None,类别权重,用于调整各类别的损失重要程度,常用于类别不均衡的情况。  
    - ignore_inex=int,指定一个目标值,忽略目标值类别不进行loss计算,一般用于处理序列预测中的填充。  
    - reduction='mean'/'sum'/'none',是否需要对loss进行降维。  
    - label_smoothing=float in [0., 1.],标签平滑参数,用于减少方差,防止过拟合,通常设置为0.01-0.1。  
    '''  
    # 以下有值的为默认值  
    torch.nn.CrossEntropyLoss(weight=None, ignore_index=-100, reduction='mean')
    
  3. MSELoss:

    # 以下有值的为默认值  
    torch.nn.MSELoss(reduction='mean')
    

优化器

  1. Optimizer:

    • 优化器的实现在torch.optim模块中,有核心类Optimizer。

    • 优化器对需要操作的权重进行管理,只有被管理的权重,优化器才会对其进行操作。

  2. Optimizer基类:

    • 属性:

      • 参数组(param_groups)

        • 用来管理需要进行优化的参数,例如权值weight、偏置bias、BN的alpha/beta等等。

        • 参数组是一个list,其元素是一个dict,网络的参数与自定义超参数。

      • state
        用于存储优化策略中需要保存的一些缓冲值,例如用momentum时,需要保存之前的梯度。

      • defaults
        优化方法默认的超参数。

    • 方法:

      • zero_grad():清零所管理参数的梯度,即手动清0梯度。

      • step():执行一步更新,依据当前的梯度进行更新参数。

      • add_param_group(param_group):给optimizer管理的参数组中增加彝族参数,可以定制超参数。

      • state_dict():获取当前state属性。

      • load_state_dict(state_dict):加载所保存的state属性,恢复训练状态。

  3. SGD使用:

    优化器类继承于Optimizer类,并重写step函数。

    1. 实例化:optimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.9, weight_decay=5e-4)
    2. loss.backward()之前进行梯度清零:optimizer.zero_grad()
    3. loss.backward()之后执行一步更新:optimizer.step()
  4. SGD函数:

    '''  
    参数说明:
    - params=iterable,要优化的参数,传递给优化器的模型参数的迭代器(Variable或dict)。  
    - lr=float,学习率,决定参数更新的步长。    
    - momentum=float,动量因子。 
    - weight_decay=float,L2正则化权重衰减系数。  
    - dampening=float,动量阻尼因子。  
    - nesterov=Bool,是否使用Nesterov动量,可以提高模型的性能。  
    - maximize=Bool,是否最大化目标函数,而不是最小化。  
    - foreach=Bool,是否使用foreach实现优化参数更新,foreach是一个优化路径。  
    - differentiable=Bool,是否创建可微SGD更新。  
    '''  
    
    # 以下有值的为默认值  
    torch.optim.SGD(params, lr=0.001, momentum=0, dampening=0, weight_decay=0, nesterov=False, *, maximize=False, foreach=None, differentiable=False)
    
  5. Adagrad函数:

    '''  
    参数说明:
    - params=iterable,要优化的参数,传递给优化器的模型参数的迭代器(Variable或dict)。  
    - lr=float,学习率,决定参数更新的步长。    
    - ld_decay=float,学习率衰减系数。 
    - weight_decay=float,L2正则化权重衰减系数。  
    - initial_accumulator_value=float,累积器的初始值。存储参数平方梯度的累计值的初始值。  
    - eps=float,防止分母为0。  
    - nesterov=Bool,是否使用Nesterov动量,可以提高模型的性能。  
    - maximize=Bool,是否最大化目标函数,而不是最小化。  
    - foreach=Bool,是否使用foreach实现优化参数更新,foreach是一个优化路径。  
    - differentiable=Bool,是否创建可微SGD更新。  
    '''  
    
    # 以下有值的为默认值  
    torch.optim.Adagrad(params, lr=0.01, lr_decay=0, weight_decay=0, initial_accumulator_value=0, eps=1e-10, foreach=None, *, maximize=False, differentiable=False)
    
  6. Adam函数:

    '''  
    参数说明:
    - params=iterable,要优化的参数,传递给优化器的模型参数的迭代器(Variable或dict)。  
    - lr=float,学习率,决定参数更新的步长。    
    - betas=(float, float),用于计算一阶和二阶矩的系数。 
    - weight_decay=float,L2正则化权重衰减系数。  
    - amsgrad=Bool,是否使用AMSGrad变体。  
    - eps=float,防止分母为0。    
    - maximize=Bool,是否最大化目标函数,而不是最小化。  
    - foreach=Bool,是否使用foreach实现优化参数更新,foreach是一个优化路径。  
    - differentiable=Bool,是否创建可微SGD更新。  
    - fused=Bool,是否使用融合的更新步骤,可以提高某些硬件上的性能。  
    '''  
    
    # 以下有值的为默认值
    torch.optim.Adam(params, lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False, *, foreach=None, maximize=False, capturable=False, differentiable=False, fused=None)
    

学习率调整器

  1. lr_scheduler模块的设计与优化器一样,定义了一个核心基类_LRScheduler,在_LRScheduler中设计好学习率调整器的属性和方法。

  2. _LRScheduler类的属性与方法:

    • 核心属性:

      • optimizer:调整器所管理的优化器的引用。
      • base_lrs:基础学习率,来自于optimizer一开始设定的值。
      • last_epoch:是记录迭代次数,通常用于计算下一轮学习率,默认为-1。
    • 核心属性:

      • state_dict():获取调整器的状态数据。
      • load_state_dict():加载状态数据。
      • get_last_lr():获取上一次学习率。
      • get_lr():获取当前学习率。
      • print_lr():打印学习率。
      • step():更新学习率。
  3. lr_scheduler使用流程:

    1. 实例化:scheduler = optim.lr_scheduler.StepLR(optimizer, gamma=0.1, step_size=50)
    2. 合适的位置执行step(),不同调整器的更新策略不同,有的基于epoch维度,有的基于iteration维度。
  4. StepLR函数(lr_new = lr_old * gamma):

    1. 获取初始学习率:group.setdefault('initial_lr', group['lr'])
    2. 记录基础学习率:self.base_lrs = [group['inital_lr'] for group in optimizer.param_groups]
    3. 执行首次step()。
  5. StepLR函数:

    '''  
    参数说明:
    - optimizer=Optimizer,要进行学习率调整的优化器。  
    - step_size=int,学习率更新的周期,单位是epoch。    
    - gamma=float,学习率衰减的比例。 
    - last_epoch=int,最后一个训练周期的索引,确保学习率调度器能够从上次结束的地方继续。   
    '''  
    
    # 以下有值的为默认值
    torch.optim.lr_scheduler.StepLR(optimizer, step_size, gamma=0.1, last_epoch=-1)
    
  6. 迭代模块中分类预测的答案获取:max()

    '''
    返回值是tuple(max_value, max_index)
    参数说明:  
    - input=tensor,输入张量。  
    - dim=int,要寻找最大值的维度,如果没有则是在所有元素中寻找最大值。  
    - keepdim=Bool,如果为True,则返回张量会保持dim维度的大小。  
    - out=tensor,输出张量,用于储存结果。  
    '''  
    
    # 以下有值的为默认值
    torch.max(input, dim, keepdim=False, *, out)
    

pytorch的其他其他方面

模型运行及训练位置选择

  1. 一般模型默认为运行在CPU上,如需运行在CUDA需要添加以下内容。

  2. 主文件头定义引用:

    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    
  3. DataLoader函数内开启pin_memory,提高在CUDA上运行时的效率。

  4. 创建实例时model.to(device=device),让模型运载在CUDA上。

  5. 将数据加载到CUDA上去,data, labels = data.to(device), labels.to(device)

模型的保存与加载

  1. 把对象转换为字节序列的过程是序列化,反序列化则是把字节序列恢复为对象。

  2. 序列化函数:.save()

    '''  
    参数说明:
    - obj=object,要保存的对象。  
    - f=str,一个文件类对象(必须二进制打开)或一个包含文件名的字符串。    
    - pickle_module=Any,用于序列化元数据核对象的模块。 
    - pickle_protocol=int,序列化使用的协议。   
    - _use_new_zipfile_serialization=Bool,是否使用新的zipfile序列化格式。  
    '''  
    
    # 以下有值的为默认值
    torch.save(obj, f, pickle_module=pickle, pickle_protocol=DEFAULT_PROTOCOL, _use_new_zipfile_serialization=True)
    
  3. 反序列化函数:

    需要手动指定到cpucuda:0,否则会报错

    '''  
    参数说明:  
    - f=str,一个文件类对象(必须二进制打开)或一个包含文件名的字符串。    
    - map_location=Any,一个函数、字典或包含设备的字符串,用于指定将如何重新映射存储的。 
    - pickle_module=Any,用于解序列化元数据核对象的模块。   
    - weights_only=Bool,是否只加载模型权重而不加载模型的架构。  
    - mmap=Bool,是否以 将被使用内存映射文件的方式加载。
    - pickle_load_args=Any,传递给pickle_module.load的额外关键字参数。  
    '''  
    
    # 以下有值的为默认值
    torch.load(f, map_location=None, pickle_module=pickle, *, weights_only=False, mmap=None, **pickle_load_args)
    
  4. 保存模型的方式:

    • 保存整个模型:
      不常用,且在加载过程中还需要指定的类方法。

    • 保存模型参数:

      # 示例
      net_state_dict = net.state_dict()
      torch.save(net_state_dictm "my_model.pth")
      
  5. 常用代码模板段:

    path_save = "model_saved.pth"
    # =================== save ===============
    checkpoint = {
        # model_without_ddp封装的是非分布式数据并行模型(分布式封装在DistributedDataParallel模块)
        "model": model_without_ddp.state_dict(),
        "optimizer": optimizer.state_dict(),
        "lr_scheduler": lr_scheduler.state_dict(),
        "epoch": epoch,
        }
    torch.save(checkpoint, path_save)
    
    # =================== resume ===============
    
    if os.path.isfile(path_save):
        # 如果文件存在,加载 checkpoint
        checkpoint = torch.load(path_save, map_location="cuda:0")
        model.load_state_dict(checkpoint["model"])
        optimizer.load_state_dict(checkpoint["optimizer"])
        lr_scheduler.load_state_dict(checkpoint["lr_scheduler"])
        start_epoch = checkpoint["epoch"] + 1
    
    else:
        # 如果文件不存在,初始化模型、优化器和学习率调度器
        print(f"Checkpoint file '{path_save}' does not exist. Starting from scratch.")
        start_epoch = 0
    
    

torch中CUDA相关函数

  1. torch.cuda.device_count():查看可用GPU数量。

  2. torch.cuda.current_device():查看当前使用的设备的序号。

  3. torch.cuda.get_device_name():获取设备的名称。

  4. torch.cuda.get_device_capability(device=None):查看设备的计算力。

  5. torch.cuda.is_available():查看cuda是否可用。

  6. torch.cuda.get_device_properties():查看GPU属性。

  7. torch.cuda.mem_get_info(device=None):查询GPU空余显存以及总显存。

  8. torch.cuda.memory_summary(device=None, abbreviated=False):类似模型的summary,它将GPU的详细信息进行输出。

  9. torch.cuda.empty_cache():清空缓存,释放显存碎片。

  10. torch.backends.cudnn.benchmark=True: 提升运行效率,仅适用于输入数据较固定的,如卷积会让程序在开始时花费一点额外时间,为整个网络的每个卷积层搜索最适合它的卷积实现算法,进而实现网络的加速让内置的cuDNN的 auto-tuner 自动寻找最适合当前配置的高效算法,来达到优化运行效率的问题

  11. torch.backends.cudnn.deterministic=True: 用以保证实验的可重复性,由于cnDNN 每次都会去寻找一遍最优配置,会产生随机性,为了模型可复现。

多GPU运算

  1. 单机多GPU运算过程:

    1. 数据平均划为N份;
    2. 模型参数复制N份;
    3. 在N个GPU上同时运算;
    4. 回收N个GPU上的运算结果。
  2. 单机多GPU运算:DataParallel

    '''  
    参数说明:  
    - module=module,需要并行的module。    
    - device_ids=list,默认采用所有可见GPU,可设置id列表选择需要的GPU。 
    - pickle_module=Any,用于解序列化元数据核对象的模块。   
    - output_device=device/int,设置输出结果所在设备。  
    '''  
    
    # 以下有值的为默认值
    torch.nn.DataParallel(module, device_ids=None, output_device=None, dim=0)
    
  3. DataParallel作用主要模块:

    • 数据切分:
      inputs, kwargs = self.scatter(inputs, kwargs, self.device_ids)

    • 模型(复制)分发:
      replicas = self.replicate(self.module, self.device_ids[:len(inputs)])

    • 执行并行推理:
      outputs = self.parallel_apply(replicas, inputs, kwargs)

    • 结果回收:
      return self.gather(outputs, self.output_device)

  4. 当model变为了Dataparallel时,参数名称会多一个module.字段,导致保存时也会多了module.字段,所以有两种方法应对:

    • 删除字段:

      from collections import OrderedDict
      new_state_dict = OrderedDict()
      for k, v in state_dict_load.items():
          namekey = k[7:] if k.startswith('module.') else k
          new_state_dict[namekey] = b
      
    • checkpoint内加载字段:

      checkpoint = {'model_state_dict': model.module.state_dict(),  # 注意这里需要使用 'module'
                    'optimizer_state_dict': optimizer.state_dict(),
                    'epoch': epoch,
                    'loss': loss
                    }
      
  5. 使用指定编号的GPU:
    os.environ.setdefault("CUDA_VISIBLE_DEVICES", "2, 3")这里是物理设备的2、3号GPU,在程序中表示0、1号GPU,注:CUDA_VISIBLE_DEVICES要在device = torch.device("cuda" if torch.cuda.is_available() else "cpu")之前使用!

pytorch实现MNIST示例

import torch
from torchvision import datasets, transforms
from torchvision.transforms import v2
from torch.utils.data import DataLoader, random_split
from collections import OrderedDict
from torch import nn, optim
import os
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")


def main():
    """主函数"""

    # 自定义参数
    epoch_num = 200
    train_batch_size = 50
    test_batch_size = 1000
    validation_batch_size = 100
    path_save = "model_saved.pth"

    '''数据模块'''
    # 数据增强与图像转换Tensor
    transform = v2.Compose([v2.Resize((32, 32),),
                            transforms.ToTensor(),
                            ])

    # 获取数据集
    train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
    test_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform)

    # 划分验证集
    validate_rate = 0.1
    train_dataset_size = len(train_dataset)
    validation_size = int(validate_rate * train_dataset_size)
    train_dataset, validation_dataset = random_split(train_dataset,
                                                     [train_dataset_size-validation_size, validation_size])

    # 加载数据集
    train_loader = DataLoader(train_dataset, batch_size=train_batch_size, shuffle=True, pin_memory=True)
    test_loader = DataLoader(test_dataset, batch_size=test_batch_size, shuffle=False, pin_memory=True)
    validation_loader = DataLoader(validation_dataset, batch_size=validation_batch_size, shuffle=False, pin_memory=True)

    '''模型模块'''
    class SuperBoy(nn.Module):
        # 初始化
        def __init__(self):
            super().__init__()
            self.part1 = nn.Sequential(OrderedDict([('conv1', nn.Conv2d(1, 32, (3, 3))),
                                                    ('relu1', nn.ReLU()),
                                                    ('pool1', nn.MaxPool2d((2, 2), 2)),
                                                    ('conv2', nn.Conv2d(32, 64, (3, 3))),
                                                    # ('BN1', nn.BatchNorm2d(64)),
                                                    ('relu2', nn.ReLU()),
                                                    ('pool2', nn.MaxPool2d((2, 2), 1)),
                                                    ]))
            self.part2 = nn.Sequential(OrderedDict([('linear1', nn.Linear(64*12*12, 200)),
                                                    ('BN2', nn.BatchNorm1d(200)),
                                                    ('relu3', nn.ReLU()),
                                                    ('dropout', nn.AlphaDropout(inplace=True, p=0.4)),
                                                    ('linear2', nn.Linear(200, 10)),
                                                    ]))
            self.part1.conv1.weight.data = nn.init.kaiming_normal_(self.part1.conv1.weight.data)
            self.part1.conv2.weight.data = nn.init.kaiming_normal_(self.part1.conv2.weight.data)
            self.part2.linear1.weight.data = nn.init.kaiming_normal_(self.part2.linear1.weight.data)
            self.part2.linear2.weight.data = nn.init.kaiming_normal_(self.part2.linear2.weight.data)

        # 前向传播
        def forward(self, x): 
            x = self.part1(x)
            x = x.view(x.size(0), -1)   # 在这里x.size(0)是Batch_size,剩余的自动计算展开
            out = self.part2(x)
            return out

    model = SuperBoy()
    model.to(device=device)

    '''优化模块'''
    loss_f = nn.CrossEntropyLoss()
    optimizer = optim.Adam(model.parameters(), lr=0.0005, weight_decay=0.0001)

    if os.path.isfile(path_save):
        # 如果文件存在,加载 checkpoint
        checkpoint = torch.load(path_save, map_location="cuda:0")
        model.load_state_dict(checkpoint["model"])
        optimizer.load_state_dict(checkpoint["optimizer"])
        start_epoch = checkpoint["epoch"] + 1

    else:
        # 如果文件不存在,初始化模型、优化器和学习率调度器
        print(f"Checkpoint file '{path_save}' does not exist. Starting from scratch.")
        start_epoch = 0

    # 判断是否进入训练模式
    if start_epoch < epoch_num:

        '''迭代模块'''
        for epoch in range(start_epoch, epoch_num):
            # 训练集训练
            model.train()

            correct_per_epoch = 0
            train_labels_sum = 0
            loss_per_epoch = 0
            count = 0

            for train_data, train_labels in train_loader:
                train_data, train_labels = train_data.to(device), train_labels.to(device)
                count += 1

                # forward & backward
                outputs = model(train_data)
                optimizer.zero_grad()

                # loss 计算
                loss = loss_f(outputs, train_labels)
                loss.backward()
                optimizer.step()
                loss_per_epoch += loss

                # 计算分类准确率
                _, predicted = torch.max(outputs, 1)
                correct_num = (predicted == train_labels).sum().item()
                correct_per_epoch += correct_num
                train_labels_sum += train_labels.size(0)

            loss_per_epoch /= count
            acc_train = correct_per_epoch / train_labels_sum
            print(f"Epoch:{epoch} Train Loss:{loss_per_epoch:.2f} Acc:{acc_train:.1%}")

            # 验证集验证
            model.eval()

            correct_per_epoch = 0
            valid_labels_sum = 0
            loss_per_epoch = 0
            count = 0

            for valid_data, valid_labels in validation_loader:
                valid_data, valid_labels = valid_data.to(device), valid_labels.to(device)
                count += 1

                # forward
                outputs = model(valid_data)

                # loss 计算
                loss = loss_f(outputs, valid_labels)
                loss_per_epoch += loss

                # 计算分类准确率
                _, predicted = torch.max(outputs, 1)
                correct_num = (predicted == valid_labels).sum().item()
                correct_per_epoch += correct_num
                valid_labels_sum += valid_labels.size(0)

            loss_per_epoch /= count
            acc_valid = correct_per_epoch / valid_labels_sum
            print(f"Epoch:{epoch} Validation Loss:{loss_per_epoch:.2f} Acc:{acc_valid:.1%}")

            # 每个epoch自动保存参数
            checkpoint = {
                "model": model.state_dict(),
                "optimizer": optimizer.state_dict(),
                "epoch": epoch,
                }
            torch.save(checkpoint, path_save)

    else:

        '''测试模块'''
        def model_test():

            for test_data, test_labels in test_loader:
                test_data, test_labels = test_data.to(device), test_labels.to(device)

                # forward & backward
                test_outputs = model(test_data)
                optimizer.zero_grad()

                # loss 计算
                test_loss = loss_f(test_outputs, test_labels)

                # 计算分类准确率
                _, test_predicted = torch.max(test_outputs, 1)
                test_correct_num = (test_predicted == test_labels).sum().item()
                acc_test = test_correct_num / test_labels.size(0)

                print(f"Test Loss:{test_loss:.2f} Acc:{acc_test:.1%}")

        '''已训练好模型的使用'''
        model.eval()
        action = input("Enter you next action plz:")
        if action == "test":
            model_test()


if __name__ == "__main__":
    main()

本文为笔者学习《PyTorch实用教程》(第二版)的笔记(并对部分内容作了自己的补充与理解),感谢余霆嵩大佬对中文论坛内pytorch教程的贡献!

posted @ 2024-04-06 01:02  LPF05  阅读(43)  评论(0编辑  收藏  举报