没有系统学习过pytorch,最近复习了tensorflow,趁下班时间把这些pytorch复习一下。
第一章:PyTorch深度学习基础知识
这一部分的内容都很基础,原先就已经掌握了,之后会多记录一下有必要记录的点
第二章:PyTorch基础知识
2.1 张量:
之前有tf1的经验,其中常量的创建用tf.constant,与pytorch不同,在pytorch中,使用如torch.tensor(1.0, dtype=torch.float)的方式来创建,注意到一般dtype的与实际给的data类型应该一样,如果不一样会有warning,但是如果某个入参data的类型未知,则可以用dtype来做强制转换。
可以通过把一个用g = np.array([[1, 2, 3],[4, 5, 6]])创建的ndarray直接用h = torch.tensor(g) 或 i = torch.from_numpy(g)的方式将g变成一个tensor,也可以通过j = h.numpy()的方式将tensor转成numpy类型。
以下是常见的构造Tensor的函数:
以及查看tensor的维度信息用k.shape或k.size(),以及torch.add(k, l)等,索引方式,用view改变tensor形状。
tensor的广播机制,当维度不一样的时候运算时会对齐维度,注意。
拓展与压缩tensor维度,如r = o.unsqueeze(1)会在第将torch.Size([2, 3])的张量变成torch.Size([2, 1, 3])的。此时,假设我们使用r.squeeze(0)去压缩维度,其Size并不会变化因为该处维度并不是1,而用r.squeeze(1)则确实可以实现想要的效果。
2.2 自动求导:
接下去关于自动求导,基本都会的,Tensor数据结构是实现自动求导的基础,数学基础是多元函数求导的雅可比矩阵及复合函数的链式求导。
动态求导用动态计算图(DCG)具体实现,即张量和运算结合起来创建动态计算图,动静态图的区别主要在是否需要预先定义计算图的结构。
用x1.grad.data查看梯度,在未反向传播时值为None,通过如y.backward(),导数会累积,重复运算相同命令grad会增加,因此每次计算前要清除当前导数值避免累积,记得设置张量的requires_grad参数为True
2.3 并行计算:
接下去是并行计算,为什么?(能计算——显存占用,算得快——计算速度,效果好——大batch提升训练效果),cuda,并行的三种方法(网络结构分布到不同设备中Network Partitioning,同一层的任务发布到不同设备中Layer-wise Partitioning,不同数据发布到不同的设备中Data Parallelism)。cuDNN时用于深度神经网络的加速库,cuDNN基于cuda完成深度学习的加速。
第三章:PyTorch的主要组成模块
3.1 思考:完成深度学习的必要部分:
和机器学习类似,在完成任务时,我们都先要进行数据预处理,其中重要的步骤包括如数据格式统一和必要的数据变换,同时划分训练集和测试集。接下来选择模型,并设定损失函数和优化函数,以及对应超参,最后用模型去拟合训练集数据,并在验证集/测试集上计算模型表现。
当然深度学习也有一些特殊性,如1. 样本量大,通常需要分批batch加载 2. 逐层、模块化搭建网络 3. 多样化的损失函数和优化器设计 4. GPU的和使用 5. 以上各模块的配合。
3.2 基本配置:
主要有三部分1. 导入必要的packages 2. 配置训练过程的超参数如batch size,learning rate,max_epochs和num_works 3. 配置训练用的硬件设备
3.3 数据读入:
数据读入是通过Dataset+DataLoader的方式完成,Dataset定义好数据的格式和数据变换形式,DataLoader用iterative的方式不断读入批次数据。Dataset主要包含三个函数:
在构建训练和测试数据完成后,需要定义DataLoader类以在训练和测试时加载数据,以下是一点样例代码,包括了Dataset和DataLoader的创建:
3.4 模型构建:
神经网络的构造,基于nn.Module,主要有初始化函数和forward函数(backward自动实现),通过层定义+层顺序的方式构建起来。
Parameter类是Tensor的子类,如果一个Tensor是Parameter,那么它会自动被添加到模型的参数列表,当然还可以使用ParameterList和ParameterDict分别定义参数的列表和字典。一个模型的可学习参数可以通过net.parameters()返回,如params = list(net.parameters())
一个神经网络的典型训练过程如下:
- 定义包含一些可学习参数(或者叫权重)的神经网络
- 在输入数据集上迭代
- 通过网络处理输入
- 计算loss(输出和正确答案的距离)
- 将梯度反向传播给网络的参数
- 更新网络的权重,一般使用一个简单的规则:weight = weight - learning_rate * gradient
torch.nn只支持小批量处理(mini-batch)不支持单个样本的输入,可以通过比如input.unsqueeze(0)来拓展维度。
3.5 模型初始化:
PyTorch在torch.nn.init中为我们提供了常用的初始化方法。初始化函数多种多样,一点案例如下,也可以用如initialize_weights()的函数将各种初始化方法放在一起。
3.6 损失函数:
常用操作backward(),主要有以下一些损失函数:
- 二分类交叉熵损失函数torch.nn.BCELoss,一般来说input为sigmoid激活层的输出或者softmax的输出
- 交叉熵损失函数torch.nn.CrossEntropyLoss
- L1损失函数torch.nn.L1Loss
- MSE损失函数torch.nn.MSELoss,差的平方
- 平滑L1损失函数torch.nn.SmoothL1Loss,其功能是减轻离群点带来的影响
- 目标泊松分布的负对数似然损失torch.nn.PoissonNLLLoss
- KL散度torch.nn.KLDivLoss
- MarginRankingLoss
- 多标签边界损失函数torch.nn.MultiLabelMarginLoss,对于多标签分类问题计算损失函数
- 二分类损失函数torch.nn.SoftMarginLoss,计算二分类的logistic损失
- 多分类的折页损失torch.nn.MultiMarginLoss,计算多分类的折页损失
- 三元组损失torch.nn.TripletMarginLoss
- HingeEmbeddingLoss,对输出的embedding结果做Hinge损失计算
- 余弦相似度torch.nn.CosineEmbeddingLoss,对两个向量做余弦相似度
- CTC损失函数,torch.nn.CTCLoss,CTC损失函数
3.7 训练和评估:
模型状态设置:model.train(), model.eval(),模型训练流程包括读取、转换、梯度清零、输入、计算损失、反向传播、参数更新,验证流程包括读取、转换、输入、计算损失、计算指标。训练和评估的代码样例如下,注意区别。
3.8 可视化:
略
3.9 PyTorch优化器:
常见的优化器如下,这些算法都继承于Optimizer,其有三个属性,defaults存储的是优化器的超参数,state参数的缓存,param_groups管理的参数组,Optimizer还有这些方法zero_grad(), step(), add_param_group(), load_state_dict(), state_dict()
- torch.optim.ASGD
- torch.optim.Adadelta
- torch.optim.Adagrad
- torch.optim.Adam
- torch.optim.AdamW
- torch.optim.Adamax
- torch.optim.LBFGS
- torch.optim.RMSprop
- torch.optim.Rprop
- torch.optim.SGD
- torch.optim.SparseAdam
第四章:PyTorch基础实战
比较简单,我也不做CV,略
第五章:PyTorch模型定义
5.1 PyTorch模型定义的方式
Module类是torch.nn模块里提供的一个模型构造类(nn.Module),是所有神经网络模块的基类,我们可以继承它来定义我们想要的模型。PyTorch模型定义应包括两个主要部分:各个部分的初始化(__init__)
;数据流向定义(forward),基于nn.Module,我们可以通过Sequential, ModuleList和ModuleDict三种方式定义PyTorch模型。
Sequential,对应模块为nn.Sequential(),当模型的前向计算为简单串联各个层的计算时,Sequential类可以通过更简单的方式定义模型。它可以接收一个子模块的有序字典(OrderedDict)或者一系列字模块作为参数来逐一添加Module的实例,而模型的前向计算就是将这些实例按添加的顺序逐一计算。使用Sequential定义模型的好处在于简单、易读,同时使用Sequential定义的模型不需要再写forward,因为顺序已经定义好了。但缺点是会使得模型定义失去灵活性,比如需要在模型中间加入一个外部输入时就不适合用Sequential的方式实现。结合Sequential和定义方式加以理解:
如下是两种用Sequential来定义模型的例子:
ModuleList,对应模块为nn.ModuleList(),其接收一个子模块(或层,需属于nn.Module类)的列表作为输入,也可以类似list那样进行append和extend操作。同时,子模块或层的权重也会自动添加到网络中来。
要特别注意的是,nn.ModuleList并没有定义一个网络,它只是将不同的模块储存在一起。ModuleList中元素的先后顺序并不代表其在网络中的真实位置顺序,需要经过forward函数指定各个层的先后顺序才算完成了模型的定义。具体实现时用for循环即可完成。
ModuleDict,对应模块为nn.ModuleDict(),其与ModuleList作用类似,只是ModuleDict能更方便地为神经网络的层添加名称。
Sequential适用于快速验证结果,因为已经明确了要用哪些层,总结写一下就好了,不需要同时写__init__
和forward。ModuleList和ModuleDict在某个完全相同的层需要重复出现多次时非常方便实现,可以一行顶多行。当我们需要之前层的信息的时候,比如ResNets中的残差计算,当千层的结果和之前层中的结果进行融合,一般使用ModuleList/ModuleDict比较方便。
5.2 利用模型快速搭建复杂网络
一个U-Net的例子,比较常规,没什么要特别注意的点。
5.3 PyTorch修改模型
修改模型若干层,可以deepcopy某一层然后修改一下参数。添加额外输入,可以在加forward参数个数。添加额外输出,直接加forward的返回值。
5.4 PyTorch模型保存与读取
PyTorch存储模型主要采用pkl, pt, pth三种格式,就使用层面没有区别。
一个PyTorch模型有俩部分:模型结构与权重,其中模型是继承nn.Module的类,权重的数据结构是一个字典。对于单卡而言方式如下没有太大区别,除了大小差了一点:
对于多卡情况下就不一样了,PyTorch中将模型和数据放到GPU上有两种方式————.cuda()和.to(device),暂时对前面一种方式讨论。如果要使用多卡训练的话,需要对模型使用torch.nn.DataParallel,代码样例如下:
查看model对应layer名称,可以看到差别在于多卡并行的模型每层的名称前多了一个“module”,如单卡是layer: conv1.weight,多卡是layer: module.conv1.weight。这种模型表示的不同可能会导致模型保存和加载过程中因为单GPU和多GPU环境的不同带来模型不匹配等问题,需要处理一些矛盾点,以下做分类讨论:
- 单卡保存+单卡加载:这个比较简单,即使保存和读取时使用的GPU不同也无妨。
- 单卡保存+多卡加载:这种情况比较简单,读取单卡保存的模型后,用nn.DataParallel进行分布式训练即可。
- 多卡保存+单卡加载:此时核心问题是如何去掉权重字典键名中的“module”,以保证模型统一性,对于加载整个模型,直接提取模型的module属性即可。对于加载模型圈子,有多种思路:去除字典里的module麻烦,往model里添加module简单(推荐),这样即便是单卡也能开始训练(相当于分布到单卡上);遍历字典去除module;使用replace操作去除module。
- 多卡保存+多卡加载:由于模型保存和加载都用多卡,因此不存在模型层名前缀不同的问题。但多卡状态下存在一个device(使用的GPU)匹配的问题,既保存整个模型时会同时保存所使用的GPU id等信息,读取时若这些信息和当前使用的GPU信息不符可能会报错或者程序不按预定状态运行,具体表现为:1. 读取整个模型再使用nn.DataParallel进行分布式训练设置————这种情况很可能会造成保存的整个模型中GPU id和读取环境下设置的GPU id不符,训练时数据所在device和模型所在device不一致而报错。2. 读取整个模型而不使用nn.DataParallel进行分布式训练设置————这种情况可能不会报错,测试中发现程序会自动使用设备的前n个GPU进行训练(n是保存的模型使用的GPU个数)。此时如果指定的GPU个数少于n,则会报错。在这种情况下,只有保存模型时环境的device id和读取模型时环境的device id一致,程序才会按照预期在指定的GPU上进行分布式训练。相比之下,读取模型权重,之后再使用nn.DataParallel进行分布式训练设置则没有问题。因此多卡模式下建议使用权重的方式存储和读取模型。如果只有保存的整个模型,也可以采用提取权重的方式构建新的模型。
另外,上面所有对于loaded_model修改权重字典的形式都是通过赋值来实现的,在PyTorch中还可以通过”load_state_dict”函数来实现。
第六章:PyTorch进阶训练技巧
6.1 自定义损失函数
这个比较简单,就是继承nn.Module类然后实现一下__init__
和forward函数即可。在自定义损失函数时,涉及到数学运算时,我们最好全程使用PyTorch提供的张量计算接口,这样就不需要我们实现自动求导功能并且我们可以直接调用cuda,使用numpy或者scipy的数学运算时,操作会有些麻烦。
6.2 动态调整学习率
可以用scheduler来实现学习率的动态调整torch.optim.lr_scheduler
- lr_scheduler.LambdaLR
- lr_scheduler.MultiplicativeLR
- lr_scheduler.StepLR
- lr_scheduler.MultiStepLR
- lr_scheduler.ExponentialLR
- lr_scheduler.CosineAnnealingLR
- lr_scheduler.ReduceLROnPlateau
- lr_scheduler.CyclicLR
- lr_scheduler.OneCycleLR
- lr_scheduler.CosineAnnealingWarmRestarts
6.3 模型微调
把requires_grad设置成False来实现冻结参数微调其他参数。
6.4 半精度训练
如用from torch.cuda.amp import autocast然后加相应装饰器的方式做半精度训练,训练和评估时加一个with autocast():
第七章:PyTorch可视化
7.1 可视化网络结构
使用torchinfo工具包,可视化网络结构需要进行一次前向传播以获得特定层的信息。
7.2 CNN可视化
只针对CNN,不感兴趣。不过对hook机制的解释很形象有趣:可以想象这样的场景,数据通过网络向前传播,网络某一层我们预先设置了一个钩子,数据传播后钩子上会留下数据在这一层的样子。这个hook机制还要搞熟练。
7.3 使用TensorBoard可视化训练过程
这个也用过了tensorboard