Pytorch 学习笔记
文章持续更新中
本篇用于记录深度学习框架 Pytorch 使用过程中遇到的问题,或分享记录一些算法实现过程中重复性的代码模板。
1 Pytorch 使用碰过的 bug
tensor
运算一定要在同一个设备中nn.utils.clip_grad_norm
已弃用替换为torch.nn.utils.clip_grad_norm_
- windows 下
dataloader
不支持多线程读取数据 - 注意
tensor
与numpy
的转换,在GPU上的tensor
先转到CPU再转为numpy
2 模型相关
model
的网络模型。2.1 model.train()
当训练和测试同一个model时,注意不要忘记在训练时将模型转换为训练模式,
执行该语句模型会转变为训练模式,从而开启模型的 Dropout
和 Batchnormal
层,最好写在训练循环中。
源码中实际上是将 model
的每个子 module.train
都置为 True
。
2.2 model.eval()
测试模式,关闭 Dropout
和 Batchnormal
层。
使用是需要在模型测试模块代码中加入 with torch.no_grad():
来停止自动求梯度是为了加快GPU运算速度。
2.3 model.apply()
用于初始化模型参数,传入初始化函数,例如:
|
|
torch.nn.init
中包含多个初始化函数,如
.uniform_
均匀分布.normal_
正态分布.constant_
常数.xavier_uniform_
Xavier 初始化.kaiming_uniform_
Kaiming 初始化
2.4 nn.utils.clip_grad_norm_()
训练时对模型参数进行梯度裁剪,防止梯度过大或者梯度消失,三个参数(模型参数, 梯度最大范围, 范数类型)
注意该方法只在训练时使用,并且需要在loss.backward()
之后和optimizer.step()
之间调用。例如:
|
|
2.5 引入与训练模型
例如引入resnet18 网络模型。
|
|
3 优化器 optimizer 相关
优化其中需要传入模型的参数,优化器中保存的是模型参数的地址。
3.1 optimizer.zero_grad()
Pytorch 中的梯度计算不自动清零,在每次训练循环中要注意清空优化器中的梯度。 通常我们训练时都会使用 mini-batch 。每次迭代 dataloader 数据取出一个 batch 的 x 和 y , 因此如果在迭代 dataloader 时不将梯度清零,则 optimizer 中会累加上一个 batch 的梯度。
3.2 optimizer.step()
该方法用于参数更新,优化器会遍历整个参数列表,根据优化器种类更新每个参数。
注意 step 要在loss.backward()
之后使用。
具体可查看如下 pytorch 源码,SGD 算法中更新步骤的源码实现,其中计算了动量,权重衰减等参数。
|
|
3.3 动态学习率 torch.optim.lr_scheduler
pytorch 提供了动态改变学习率的方法,封装在 torch.optim.lr_scheduler
之中,我们也可以自定义 lambda 函数来自定义学习率的变化。
scheduler 更新需要在每个 batch 遍历完进行,也就是放到 epoch 的循环内。
栗子:
|
|
4 损失函数
这里用 MSELoss 举例:
$$ \ell(x, y) = L = {l_1,\dots,l_N}^\top, \quad
l_n = \left( x_n - y_n \right)^2
$$
$$
\ell(x, y) =
\begin{cases}
\operatorname{mean}(L), & \text{if reduction} = \text{‘mean’;} \\
\operatorname{sum}(L), & \text{if reduction} = \text{‘sum’.}
\end{cases}
$$
如果我们设置了 MESLoss 中参数 reduction,默认情况下会自动除 batch 的大小。如果我们想要保存整个 batch 的 loss 求和,则将 reduction 改为 sum。
|
|
5 激活函数
激活函数继承自 Module 。
Softmax
$$ \text{Softmax}(x_{i}) = \frac{\exp(x_i)}{\sum_j \exp(x_j)} $$
传入参数 dim ,之后会沿着该维度进行切片进行计算,使该维度的 tensor 和等于1,一般传入dim为-1。
一个直观的栗子:
|
|
6 tensor
- tensor 和 np 数组可以共享一块内存
- 可用用列表[]和元祖()创建,但不可以用set{} 张量操作文档
6.1 tensor 中的索引操作(gather,scatter)
用tensor.scatter_()
举例:
Tensor.scatter_(dim, index, src, reduce=None)
Writes all values from the tensor src
into self
at the indices specified in the index
tensor. For each value in src, its output index is specified by its index in src
for dimension != dim
and by the corresponding value in index
for dimension = dim
.
|
|
6.2 tensor.detach()
用于具有梯度信息的 tensor,将 tensor 从计算图中分离,即将梯度的转播断开。
detach 会返回一个新 tensor ,共享同一块数据内存,但其 requires_grad=False
。
例如:xxx.detach().cpu().numpy()
6.3 detach_()
不创建新的 tensor,直接在原来的 tensor 上修改。
6.4 tensor.item()
返回一个 python 支持的数字类型数据,只能返回标量,不能返回向量。该方法不需要考虑 tensor 是否在 CPU 或 GPU ,一般用于将 loss 转换为 numpy。
6.5 torch.max()
该 API 常用语取得预测最大值的索引,即第二个返回值。
torch.max()
的输入:
- input tensor类型数据
- dim 维度(0 表示每一列的最大值, 1 表示每一行求最大值)
返回值:
- 函数会返回两个tensor,第一个tensor是每行的最大值;第二个tensor是每行最大值的索引。
6.6 维度转换 permute()
栗子:
|
|
7 数据相关
7.1 Dataset
dataset 处理一个样本,返回一个样本的特征与标签,之后交给 dataloard 进行批处理。
自定义 Dataset 需要继承 Dataset 抽象类,实现 __init__
,__len__
,__getitem__
。
|
|
7.2 Dataloader
相当于 Dataset
的迭代器,他告诉模型用怎样的方式取去 dataset
的数据,其本身是一个可迭代对象。
常用参数:
- dataset:实例化后的dataset对象
- batch_size:批处理大小
- shuffle:是否打乱。遍历完所有的batch后将整个dataloader打乱
- sampler :数据采样方式,自定义的方式取样本,不可以指定shuffle(比如讲长度相似的样本取为一组)
- num_workers :是否用多线程读取数据
- collate_fn :对该批次的样本进行处理如pad
- pin_memory :将tensor保存在GPU中
- drop_last :如果batch_size不是数据集大小的整数倍,设置true则丢弃最后一批的数据
pin_memory
,通常情况下,数据在内存中要么以锁页的方式存在,要么保存在虚拟内存(磁盘)中,设置为 True
后,数据直接保存在锁页内存中,后续直接传入 cuda;否则需要先从虚拟内存中传入锁页内存中,再传入cuda,这样就比较耗时了,但是对于内存的大小要求比较高。
官方文档
一个十分清晰的源码解读教程7.2.1 迭代 Dataloader
返回的数据形式与 dataset 里的 getitem 返回方式有关。
- next 迭代返回一个 batch 数据
|
|
- enumerate 循环迭代
|
|
7.3 len(dataloader) 返回值是什么?
|
|
他返回了有多少个批次。
我们训练时一般是一个批次求一个loss,最后将所有loss相加,所以需要除批次的总数得到这次训练的loss。
7.3 torchvision.datasets.DatasetFolder
用于读图片类型分类别的数据,如每个类别的图片放在一个文件夹内。
如下文件结构:
|
|
datafolder
中实现了 find_classes
方法,该方法会自动扫描文件目录,并将每个图片的路径和类别返回。
一个栗子:
|
|
其中 lambda
函数被 datafolder
设置为 loader
参数,并在 __getitem__
的时候为每个样本数据执行一次 lambda
函数。如果有多个操作也可以传入一个函数地址。
源码如下:
其中 lodaer
即传入的 lambda
函数,在 getitem
中会调用传入的函数。
|
|
7.4 pad_sequence() 数据做padding
该模块位于 from torch.nn.utils.rnn import pad_sequence
,主要用于对不等长的特征做 padding。
参数:
- targets: list 矩阵,shape=[batch_size, N] ,N 与特征维度有关
- batch_first:默认 batch_size 在第一维度
- padding_value:填充的值
返回值:
- [batch_size, M] M 为 batch 中的最大长度
使用案例:
|
|
7.5 划分数据集 random_split()
该模块位于 torch.utils.data.random_split()
是 pytorch 提供的划分数据集函数。
参数:
- dataset (Dataset) – 要划分的数据集。
- lengths (sequence) – 要划分的长度。(直接给出想要分出两个数据集的长度即可)
- generator (Generator) – 用于随机排列的生成器。
栗子:
|
|
8 代码模板
8.1 保存checkpoint
|
|
8.2 加载 checkpoint
|
|