pytorch tutorial(持续更新)
pytorch tutorial
pytorch 的准备
安装流程
只需要在虚拟环境中安装 cuda 和 cudnn 即可,无需在真实环境中安装 cuda 和 cudnn(所以请不要自作主张跟着网上的教程下载 cuda 和 cudnn,已经试错)。
-
下载 miniconda,创建虚拟环境(python=3.11)。
-
下载显卡驱动,选择 LTS 版本。
-
注意CUDA、pytorch、cuDNN、python、显卡驱动、系统的版本匹配,不要用最新版本,要用 LTS 版本,以防其中一方无匹配版本(在 pytorch 官网指令下载的 cuda 和 pytorch 版本匹配,若与显卡驱动版本不匹配可查看历史版本)。
-
在虚拟环境中下载对应版本的 pytorch 和一些所需要的库:numpy、pandas、matplotlib...
-
在虚拟环境中下载 jupyter 包。
下载成果检验
-
nvidia-smi
可以获得当前显卡的驱动信息与支持 CUDA 的最高版本信息。 -
nvcc -V
可以获得目前已安装的 CUDA 的信息。 -
在虚拟环境中的 python 下检查安装的 pytorch、CUDA、cuDNN 的信息:
import torch print(torch.__version__) # pytorch 版本信息 print(torch.version.cuda) # cuda 版本信息 print(torch.backends.cudnn.version()) # cudnn 版本信息
pytorch 核心讲解
pytorch 核心模块
torch核心模块
-
_pycache_
文件夹:存放 python 解释器生成的字节码,后缀通常是 pyc/pyo,节省了 python 语言转换成字节码的时间。 -
_C
文件夹:存放一系列 pyi 文件,检验数据类型,辅助 C 语言代码调用,pytorch 底层计算代码由 C++编写。 -
include
文件夹:存放 C++代码,头文件用.h/.hpp 文件存放,在 python 库中值包含头文件。 -
lib
文件夹:存放了大量的.lib 和.dll 文件(静态链接库和动态链接库),被各类顶层 python api 调用。 -
autograd
文件夹:pytorch 的核心模块和概念,实现了梯度自动求导。 -
nn
文件夹:搭建网络的网络层存放模块。 -
onnx
文件夹:pytorch 模型转换到 onnx 模型表示的核心模块,存放有大量的 opse**.py 文件。 -
optim
文件夹:优化模块,即存放优化使用的方法函数,以及学习率调整模块lr_scheduler.py
。 -
util
文件夹:包含各类常用工具,比如关键的 data 文件夹、tensorboard 文件夹等等。
torchvision核心模块
-
datasets
文件夹:官方的常用数据集的数据读取函数。 -
models
文件夹:存放了经典的、可复现的、有训练权重参数可下载的视觉模型、分割模型、检测模型、视频任务模型、量化模型,可供调用和参考。 -
ops
文件夹:视觉任务特殊的功能函数。 -
transforms
文件夹:数据增强库,是 pytorch 自带的图像预处理、增强、转换工具(可满足日常需求,复杂需求可以使用 Albumentations 库)。
torchaudio核心模块
pytorch 核心数据结构 —— Tensor
Tensor的介绍
-
Tensor 即为张量。
-
pytorch 中有两个有关张量的相关概念:
-
torch.Tensor
是 tensor 的类,也是 pytroch 中的基本操作单位,类似于 np.array,但支持在 GPU 上进行计算。 -
torch.tensor
是创建张量的一个函数,用于将数据变为张量形式的数据。 -
但是一般用
tensor
表示张量这个数据结构。
-
-
在历史中 Tensor 需要经过 Variable 的包装才能实现自动求导,现 torch.Tensor 与 torch.autograd.Variable 已经合并,使得 Tensor;拥有了跟踪历史操作的功能,虽然 Variable 仍可用,但 Variable 返回值是一个 Tensor 而非原来的 Variable。
-
Vairiable 不仅能对 Tensor 包装,而且还能记录生成 Tensor 的运算(自动求导的关键)。
-
Tensor 的主要属性:
-
data
:多维数组,最核心的属性。 -
dtype
:多维数组的数据类型。 -
shape
:多维数组的形状。 -
device
:tensor 所在设备,cpu 或 cuda。 -
grad
、grad_fn
,is_leaf
,requires_grad
:同 Variable,是梯度计算中所用到的。
-
tensor相关函数
tensor 的创建
直接创建 tensor
-
直接创建 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)
-
直接创建 tensor 函数:torch.from_numpy
将 numpy 数组转换为 tensor,但是创建的 tensor 和原 array 共享同一块内存,即修改 array 或 tensor 数值,另一个都会改变。
torch.from_numpy(ndarray)
依数值创建 tensor
-
创建全 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)
-
创建全 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 参数代替此函数
-
创建全 1 和类似形状 tensor:torch.ones、torch.ones_like
同上 zeros 与 zeros_like,且 ones_like 也已不支持关键字参数。
-
创建全填充 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)
-
创建全填充类似形状 tensor:torch.full_like
同上 zeros 和 ones,仅多 fill_value 参数与 memory_format 参数(=troch.memory_format,所需内存格式),但支持正常关键词参数,并未废除。
-
创建等差的一维张量: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)
-
创建均分的一维张量: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)
-
创建对数均分的一维张量: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)
-
创建单位对角矩阵:torch.eye
''' 参数说明: - n=int,矩阵的行数。 - m=int,矩阵的列数,默认创建方阵。 ... ''' # 以下有值的为默认值 torch.eye(n, m=None, *, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)
-
创建空(不进行初始化赋值)和类似形状空 tensor:torch.empty、torch.empty_like
同上 zeros 与 zeros_like,但支持正常关键词参数,并未废除。
-
创建空张量,但可设置其在内存中的存储方式:torch.empty_strided
''' 参数说明: - stride=int tuple,张量存储在内存中的步长,即设置在内存中的存储方式。 ... ''' # 以下有值的为默认值 torch.empty_strided(*size, stride, *, dtype=None, layout=None, device=None, requires_grad=False, pin_memory=False)
依概率分布创建
-
创建高斯分布张量: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)
-
创建[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)
-
创建[0,1)均匀分布的类似形状张量:torch.rand_like
同 torch.zeros_like 之于 torch.zeros,但未废除。
-
创建自定义区间均匀分布的张量: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)
-
创建自定义区间均匀分布的类似形状张量:torch.randint_like
同 torch.zeros_like 之于 torch.zeros,但未废除。
-
创建标准正态分布的张量:torch.randn
同 torch.rand 函数
-
创建标准正态分布的类似形状张量:torch.randn_like
同 torch.zeros_like 之于 torch.zeros,但未废除。
-
创建 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)
-
创建自定义概率的伯努利分布(0-1 分布)张量:torch.bernoulli
''' 参数说明: - input=tensor,分布的概率,即生成 1 的概率。 ... ''' # 以下有值的为默认值 torch.bernoulli(input, *, generator=None, out=None)
tensor 相关操作函数
-
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_
全部填充值。 -
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 的数学操作
-
Pointwise Ops,逐元素的操作,如 abs、cos、sin、floor、pow...
-
Reduction Ops,减少元素的操作,如 argmax、argmin、all、any、mean、norm、var...
-
Comparison Ops,对比操作,如 ge、gt、le、lt、eq、argsort、isnan、topk...
-
Spectral Ops,谱操作,如短时傅里叶变换等各类信号处理的函数
-
Other Operations,其他,如 clone、diag、flip...
-
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>
-
非叶子节点在梯度反向传播结束后释放,中间变量的梯度默认不保留,如需保留可以对该节点设置
retain_grad()
。 -
gard_fn
用来记录创建张量时所用的运算,在链式求导法则中会用到。 -
在 pytorch 中,自动求导的关键是记录数据和该节点的运算。
-
在 tensor 中,数据=data、节点运算=grad_fn。
-
pytorch 是动态图,TensorFlow 是静态图。
-
动态图和静态图的区别:
-
动态图是在运算的同时搭建计算图,静态图是现搭建计算图再运算。
-
动态图在运算过程中是计算图可变动的,静态图是运算中计算图不可变。
-
-
动态图和静态图的优缺点:
-
动态图:
- 优点:易懂性、灵活性
- 缺点:性能差
-
静态图:
- 优点:部署效率高、性能优化强
- 缺点:晦涩性、调试困难
-
Autograd——自动微分
构建出来的计算图是反向的,即在神经网络中 loss 函数计算是根节点,而基础数据是叶子节点。
在根节点调用.backward()函数,即可自动反向传播计算计算图中所有节点的梯度。
-
自动求导机制通过有向无环图(DAG)实现。
-
在 DAG 中,记录数据(tensor.data)以及操作(tensor.grad_fn)。
-
操作在 pytorch 中统称为 function。
-
在pytorch中,只有浮点类型和复数类型的张量才能计算梯度,整形和布尔类型不支持梯度计算。
autograd 的使用
-
使用频率最高的自动求导函数: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)
-
普通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)
-
计算 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)
-
自定义操作函数:torch.autograd.Function(待学习)
-
梯度不会自动清零,需要通过
optimizer.zero_grad()
手动清零。 -
梯度运算依赖于叶子结点的结点,requiries_grad 默认为 True(依赖于 requires_grad=True 的结点 requires_grad-True)。
-
叶子张量不可以执行 in-place 操作(原地替换操作),执行后进行传播运算会报错。
-
从计算图中剥离数据可以使用.detach()函数,即以一个新张量的形式返回,并且新张量与旧张量共享数据,可以理解为别名。
w_detach = w.detach() id(w) == id(w_detach)
-
上下文管理器:
with torch.no_grad()
,使用with torch.no_grad()
可以暂时关闭梯度计算,减少计算开销和内存使用。
pytorch 数据模块
数据的流程:data -> Dataset -> DataLoader -> BatchData -> Model
Dataset
Dataset是一个抽象类,定义如何访问和获取数据集中的样本。
-
torch.utils.data.Dataset 是一个抽象基类,供用户继承编写自己的 dataset,实现对数据的读取。
-
Dataset 类的两个必须要实现的函数
__getitem__
和__len__
。 -
__getitem__
函数:实现读取样本的功能,通常传入索引,然后实现从磁盘中读取数据,并进行预处理及数据增强,然后返回一个样本的数据。getitem 返回的数据会在 dataloader 中组装成一个 batch。 -
__len__
函数:返回数据集的大小,如果返回值是 0 则 daraloader 会报错。 -
torchvision中也有dataset,在torchvision中的dataset是预先处理好的数据集,不需要重写
__getitem__
和__len__
方法,而util.data中的dataset是一个抽象基类,需要自己实现。
DataLoader
DataLoader是一个迭代器,负责批量加载数据集,并提供数据的迭代功能,接受dataset实例作为输入。
-
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='')
-
DataLoader 五大功能:
-
支持两种形式数据集读取:映射式(Map-style)与迭代式(Iterable-style)。Dataset 类是映射式,gititem 提供了序号到数据的映射,而迭代式是编写一个可迭代对象,从中依次获取数据。
-
自定义采样策略:可借助 Sampler 自定义采样策略。
-
自动组装成批数据:自动实现 batch 组装,且可以通过 batch_sampler 和 collate_fn 自定义组装的策略。
-
多进程数据加载:可以设置 num_workers 改变运算 CPU 核心数。
-
自动实现锁页内存(Pinning Memory):空间换时间,加快数据的读取数据。
-
-
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,这样会使得模型训练更加稳定。
-
-
DataLoader的原理:首先将dataset返回的数据解包后zip(将相同数据放在一起),然后对实际的数据进行组装并读取加载batch。
-
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 源码解读
- 如果sampler 和 batch_sampler 都为 None,batch_sampler 将使用内置的 BatchSampler,sampler 有两种情况:
dataset及常用API
Dataset和split
-
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]
-
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)
-
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
-
sampler和batch_sampler的关系:
-
auto_collation时采用的是batch_sampler。
-
sampler是数据集中单个样本的处理,返回的是单个样本的索引,而batch_smapler是一个batch的处理,返回的是一个batch的索引list。
-
通常是用batch_sampler,对应的是BatchSampler类。
-
-
BatchSampler:
# 以下有值的为默认值 torch.utils.data.BatchSampler(sampler, batch_size, drop_last) ''' 参数说明: - sampler=sampler/iterable,采样器,一般需要shuffle时传入RandomSampler,如果不需要打乱,SequentialSampler。 - drop_last=Bool,是否将使用不足一个batch的数据。 '''
-
batch_sampler就在这RandomSampler和SequentialSampler上封装了一个批抽取功能。
-
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
-
SequentialSampler部分实现:顺序迭代器
def __iter__(self) -> Iterator[int]: return iter(range(len(self.data_source)))
-
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
-
SubsetRandomSampler:索引定义子集(采样数即索引lsit长度)随机采样器
def __iter__(self) -> Iterator[int]: for i in torch.randperm(len(self.indices), generator=self.generator): yield self.indices[i]
-
Sampler本质上是迭代器,用于产生数据集的索引值序列;而BatchSampler是将Sampler采样得到的索引值进行合并,从而获得Batch索引。
WeightedRandomSampler
-
WeightedRandomSampler函数目的是加权采样从而实现数据均衡采样。
-
WeighterRandomSamlpler函数:
''' 参数说明: - weights=float sequence,每个样本的采样权重,权重总和不必为1,只需关注样本之间的比例即可。 - num_samples=int,采样数量,一般设为样本总量。 - replacement=Bool,是否有放回采样,True表示有放回。 - generator=Generator,自定义生成器,通常是默认的。 ''' # 以下有值的为默认值 torch.utils.data.WeightedRandomSampler(weights, num_samples, replacement=True, generator=None)
-
sampler构建的步骤:
-
计算各类的采样概率。
-
生成每个样本的概率(权重)。
-
实例化WeightedRandomSampler。
-
transforms
transforms是pytorch的数据增强函数库。
-
数据增强分为在线(online)和离线(offline)两种方式,离线是指在训练开始前对数据进行变换,在线则是在训练过程中每次加载数据都对数据进行变换。pytorch的transforms是在线方式。
-
运行机制:采用transforms.Compose把变换方法包装起来,放入dataset,在dataloader依次读数据时,调用dataset的getitem,使得读取每个sample时,都会根据compose中的方法依次对数据进行变换,完成数据增强。
变换方法
-
v2.Compose(list):组合多个变换。
torchvision.transforms.v2.Compose(transforms: Sequence[Callable])
-
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)
-
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)
-
Normalize:对tensor对象进行逐通道标准化(减均值再除以标准差)。
''' 参数说明: - mean=sequence,通常是通过计算训练数据集中所有图像每个通道的均值得到的。 - std=sequence,通常是通过计算训练数据集中所有图像每个通道的方差得到的,并取其平方根。 - inplace=Bool,指示是否直接在原始张量上进行变换,在compose中一般是False。 ''' # 以下有值的为默认值 torchvision.transforms.v2.Normalize(mean, std, inplace=False)
-
FiveCrop&TenCrop:神器。
自动数据增强
-
AutoAugmentPolicy:AutoAugment的自动数据增强策略,其包含多个针对特定数据集的策略,例如:针对ImageNet数据集的AutoAugmentPolicy.IMAGENET。
-
v2.AutoAugment:自动数据增强方法。
''' 参数说明: - policy=AutoAugemntPolicy,数据增强策略。 - interpolation=InterpolationMode,设置插值方法。 - fill=sequeence/number,设置填充像素的像素值,默认为0,黑色。 ''' # 以下有值的为默认值 torchvision.transforms.v2.AutoAugment(policy=AutoAugmentPolicy.IMAGENET, interpolation=InterpolationMode.NEAREST, fill=None)
-
一个数据增强策略AutoAugmentPolicy内包含很多组变换,实际应用中是固定一种变换策略,这一组变换策略是由强化学习自动搜索的,所以是自动数据增强策略。
-
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)
-
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)
-
数据增强应该被视为数据变换过程的一部分,在数据被加载到模型之前应用(即先对图作处理,然后再对将其转换为tensor)。这样可以确保模型在训练过程中能够接触到各种变换后的数据,从而提高其泛化能力。
-
三种自动数据增强方法比较:
- AutoAugment 是一种自动搜索最佳数据增强组合的方法,可以找到最优的增强策略,但需要大量计算资源。
- RandAugment 是 AutoAugment 的一个变体,通过固定增强操作的数量和强度来简化搜索过程,计算资源需求较小。
- TrivialAugment 是一种非常简单的数据增强策略,只包含两种增强操作,易于实现和理解,但可能不如其他策略有效。
torchvision 经典dataset学习
-
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
-
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
-
self.data
与self.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 ''' 参数说明:
-
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
-
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函数。
-
深度神经网络就是常说的模型(Model),一个模型包含很多网络层,多个网络层构建成一个模型。在pytorch中模型是一个Module,各网路层、模块也是module(一般Module指模型或者基类,而module指模块)。
-
Module
是所有神经网络的基类,所有模型都必须继承与Module类,并且可以嵌套(一个module可以包含多个子module)。 -
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:存储管理钩子函数 '''
-
forward
函数:forward之于Module等价于__getitem__之于Dataset,forward函数是模型每次调用的具体实现,所有的模型(module)必须实现forward函数(否则调用时会报错)。 -
模型的创建:
-
构建子模块:构建网络所需要的网络层,如卷积层、池化层、全连接层等。
-
拼接子模块:在forward函数中定义需要执行的功能,即将子模块以一定的方式拼接起来,完成对数据的向前传播。
实践化的操作:
-
写一个继承于Module的类。
-
在init函数中把需要的网络层创建好。
-
在forward函数中把模型搭建的规则完成。
-
Parameter
-
Module中的Parameter是指可学习的模型的参数,如卷积层的卷积核权重和偏置、Linear层的权重和偏置...
-
Module中的parameters()方法会返回创建的model的parameter。
Module容器 —— Containers
Sequential
-
Sequential的作用是将一系列网络层按固定的先后顺序串起来(一个整体),使得调用时数据从第一个层按顺序执行到最后一个层。
-
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()) ]))
-
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
-
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
-
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
-
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
常用网络层
-
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)
-
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)
-
Adaptive Pooling Layers:自适应池化层(保证输出的图像尺寸固定)。
torch.nn.AdaptiveMaxPool2d(output_size, return_indices=False)
-
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)
-
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)
-
-
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:现在很少采用。
-
-
Dropout Layers:
''' 参数说明: - p=float,dropout 比率,即被置零的激活单元的比例。 - inplace=Bool,是否直接修改输入张量而不是创建一个新的输出张量。 ''' # 以下有值的为默认值 torch.nn.Dropout(p=0.5, inplace=False)
-
Alpha Dropout:保持输入均值和方差不变的Dropout
''' 参数说明: - p=float,dropout 比率,即被置零的激活单元的比例。 - inplace=Bool,是否直接修改输入张量而不是创建一个新的输出张量。 ''' # 以下有值的为默认值 torch.nn.AlphaDropout(p=0.5, inplace=False)
-
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)
-
-
-
将多维展开的函数(将Conv层输出展开使得linear可以接收):
view()
Tensor.view(*shape) # 一般常用 x = x.view(x.size(0), -1)
Module常用API函数
-
设置模型训练、评估模式:
-
.eval()
:设置模型为评估模式。 -
.train(mode=True)
:设置模型为训练模式。
-
-
设置模型存放在CPU/GPU:
-
.cpu()
或.to("cpu")
:将module放到CPU上。 -
.cuda()
或.to("cuda")
:将module放到cuda上。
-
-
获取模型参数,加载权重参数:
-
.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)
-
-
管理模型的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自己。
-
-
获取某个参数或submodule:
-
.get_parameter(target)
:(target是参数完整的路径)获得target的参数。 -
.get_submodule(target)
:(target是参数完整的路径)获取target模块实例。
-
-
设置模型的参数精度:
-
.half()
:半精度16。 -
.float()
:单精度32。 -
.double()
:双精度64。 -
.bfloat16()
:特殊的半精度16,性能比普通float16更强。
-
-
对子模块执行特定功能:
-
.zero_grad(set_to_none=True)
:将模型的梯度设置为0或None。 -
.apply(fn)
:递归地应用函数fn
到模块及其所有子模块上。
-
Hook函数及Grad-CAM
hook函数
hook相当于插件,可以实现一些额外的功能,而又不用修改主体代码。把这些额外功能实现了挂在主代码上,所以叫钩子。
-
hook的出现与pytorch的计算机制有关,pytorch会在运算结束后将中间变量释放,当我们需要中间变量时,又不能修改模型主体代码时,hook便可实现。
-
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.])
-
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
-
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)
-
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
-
原理:
register_full_backward_hook实现特征图的提取,并结合Grad-CAM方法对卷积神经网络的学习模式进行可视化。 -
实现方法:
- 创建网络net;
- 注册forward_hook函数用于提取最后一层特征图;
- 注册backward_hook函数用于提取类向量(one-hot)关于特征图的梯度;
- 对特征图的梯度进行求均值,并对特征图进行加权;
- 可视化heatmap。
经典Model代码分析
-
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将数据变为一维向量。
-
-
VGG:
-
VGG中包含的主要类和函数:
-
VGG类是一个nn.module。
-
_vgg函数:vgg函数接收具体的网络参数,以此决定返回哪一个vgg模型;
-
vggxxx:定义了具体VGG所需要的参数,并调用_vgg函数得到具体模型;
-
make_layers函数:创建可抽象出来、共性的网络层函数,即网络结构图中的5次堆叠部分。
-
cfgs字典:配置各具体vgg模型所需要的参数,主要在make_layers中使用。
-
-
调用关系与逻辑:
vgg16() --> _vgg() --> make_layers --> VGG
-
-
GoogLeNet:
反复使用的模块抽象为一个Moudle类,并作为参数进行调用。
-
Resnet:
resnet的搭建是将block抽象出来提供接口,由用户自行传入,并且设定堆叠次数。
权重初始化方法
Xavier系列和Kaiming系列
-
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}}\) -
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)
-
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)
-
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)
其他方法
-
使值均匀分布\(U(a, b)\)初始化:
torch.nn.init.uniform_(tensor,a=0,b=1)
-
使值正态分布\(N(mean, std)\)初始化:
torch.nn.init.normal_(tensor, mean=0, std=1)
-
使值常数val初始化:
torch.nn.init.constant_(tensor, val)
-
使值单位矩阵初始化:
torch.nn.init.eye_(tensor)
将二维tensor初始化为单位矩阵 -
使值正交初始化:
torch.nn.init.orthogonal_(tensor, gain=1)
-
使值从正态分布\(N(0, std)\)稀疏(使每个column有一部分为0,sparsity是稀疏/0的比例)初始化:
torch.nn.init.sparse_(tensor, sparsity, std=0.01)
-
使值全0初始化:
torch.nn.init.zeros_(tensor)
-
使值全1初始化:
torch.nn.init.ones_(tensor)
-
狄拉克初始化:
torch.nn.init.dirac_(tensor, groups=1)
-
增益计算:
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
-
L1loss:
''' - reduction='mean'/'sum'/'none',是否需要对loss进行降维,即对batch的损失函数的处理,none是无处理,mean是输出平均值,sum是输出和。 ''' # 以下有值的为默认值 torch.nn.L1Loss(reductio='mean')
-
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')
-
MSELoss:
# 以下有值的为默认值 torch.nn.MSELoss(reduction='mean')
优化器
-
Optimizer:
-
优化器的实现在torch.optim模块中,有核心类Optimizer。
-
优化器对需要操作的权重进行管理,只有被管理的权重,优化器才会对其进行操作。
-
-
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属性,恢复训练状态。
-
-
-
SGD使用:
优化器类继承于Optimizer类,并重写step函数。
- 实例化:
optimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.9, weight_decay=5e-4)
。 loss.backward()
之前进行梯度清零:optimizer.zero_grad()
。loss.backward()
之后执行一步更新:optimizer.step()
。
- 实例化:
-
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)
-
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)
-
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)
学习率调整器
-
lr_scheduler
模块的设计与优化器一样,定义了一个核心基类_LRScheduler,在_LRScheduler中设计好学习率调整器的属性和方法。 -
_LRScheduler
类的属性与方法:-
核心属性:
optimizer
:调整器所管理的优化器的引用。base_lrs
:基础学习率,来自于optimizer一开始设定的值。last_epoch
:是记录迭代次数,通常用于计算下一轮学习率,默认为-1。
-
核心属性:
state_dict()
:获取调整器的状态数据。load_state_dict()
:加载状态数据。get_last_lr()
:获取上一次学习率。get_lr()
:获取当前学习率。print_lr()
:打印学习率。step()
:更新学习率。
-
-
lr_scheduler使用流程:
- 实例化:
scheduler = optim.lr_scheduler.StepLR(optimizer, gamma=0.1, step_size=50)
。 - 合适的位置执行
step()
,不同调整器的更新策略不同,有的基于epoch维度,有的基于iteration维度。
- 实例化:
-
StepLR
函数(lr_new = lr_old * gamma):- 获取初始学习率:
group.setdefault('initial_lr', group['lr'])
- 记录基础学习率:
self.base_lrs = [group['inital_lr'] for group in optimizer.param_groups]
- 执行首次step()。
- 获取初始学习率:
-
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)
-
迭代模块中分类预测的答案获取:
max()
''' 返回值是tuple(max_value, max_index) 参数说明: - input=tensor,输入张量。 - dim=int,要寻找最大值的维度,如果没有则是在所有元素中寻找最大值。 - keepdim=Bool,如果为True,则返回张量会保持dim维度的大小。 - out=tensor,输出张量,用于储存结果。 ''' # 以下有值的为默认值 torch.max(input, dim, keepdim=False, *, out)
pytorch的其他其他方面
模型运行及训练位置选择
-
一般模型默认为运行在CPU上,如需运行在CUDA需要添加以下内容。
-
主文件头定义引用:
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
-
DataLoader函数内开启
pin_memory
,提高在CUDA上运行时的效率。 -
创建实例时
model.to(device=device)
,让模型运载在CUDA上。 -
将数据加载到CUDA上去,
data, labels = data.to(device), labels.to(device)
。
模型的保存与加载
-
把对象转换为字节序列的过程是序列化,反序列化则是把字节序列恢复为对象。
-
序列化函数:
.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)
-
反序列化函数:
需要手动指定到
cpu
或cuda: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)
-
保存模型的方式:
-
保存整个模型:
不常用,且在加载过程中还需要指定的类方法。 -
保存模型参数:
# 示例 net_state_dict = net.state_dict() torch.save(net_state_dictm "my_model.pth")
-
-
常用代码模板段:
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相关函数
-
torch.cuda.device_count()
:查看可用GPU数量。 -
torch.cuda.current_device()
:查看当前使用的设备的序号。 -
torch.cuda.get_device_name()
:获取设备的名称。 -
torch.cuda.get_device_capability(device=None)
:查看设备的计算力。 -
torch.cuda.is_available()
:查看cuda是否可用。 -
torch.cuda.get_device_properties()
:查看GPU属性。 -
torch.cuda.mem_get_info(device=None)
:查询GPU空余显存以及总显存。 -
torch.cuda.memory_summary(device=None, abbreviated=False)
:类似模型的summary,它将GPU的详细信息进行输出。 -
torch.cuda.empty_cache()
:清空缓存,释放显存碎片。 -
torch.backends.cudnn.benchmark=True
: 提升运行效率,仅适用于输入数据较固定的,如卷积会让程序在开始时花费一点额外时间,为整个网络的每个卷积层搜索最适合它的卷积实现算法,进而实现网络的加速让内置的cuDNN的 auto-tuner 自动寻找最适合当前配置的高效算法,来达到优化运行效率的问题 -
torch.backends.cudnn.deterministic=True
: 用以保证实验的可重复性,由于cnDNN 每次都会去寻找一遍最优配置,会产生随机性,为了模型可复现。
多GPU运算
-
单机多GPU运算过程:
- 数据平均划为N份;
- 模型参数复制N份;
- 在N个GPU上同时运算;
- 回收N个GPU上的运算结果。
-
单机多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)
-
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)
-
-
当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 }
-
-
使用指定编号的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教程的贡献!