这里写目录标题
小实战+sequential的使用
模型结构选择:CIFAR 10 model
模型目的:
从CIFAR 10的数据集可以看出,CIFAR 10模型的主要作用是,对一个物体进行分类,辨别出一个物品属于哪个类别
模型结构:
关于卷积之后,尺寸不变的问题:
卷积,我们可以从参数规定其卷积前后的通道数,所以,我们可以认为卷积实际上,主要关注的点在于通道数,但是其尺寸也会在卷积的过程中发生变化,这种变化是可控的,也就是说,我们可以通过设置padding、stride、dilation等参数,控制卷积过程中尺寸的变化
由于上图中,每次卷积前后的尺寸都以给出,所以可以根据该关系式,计算出padding、stride、dilation设置为多少(其中如果没有对dilation参数传参,那么其默认为1)
最后经过计算,padding等于2,stride等于1
关于池化:
池化,在上面的模型中,主要用于改变数据的尺寸
关于Flatten:
之前我们使用的Flatten是一个API,现在,其他他也有模型层,就叫Flatten,所以,我们可以在模型搭建时,就加上Flatten
关于最后的线性层:
图中少化了一个input,实际上,将数据处理完之后,展开成一维的结果,就是一个线性处理中的输入层,大小是64 * 4 * 4
之后进行一次线性层的处理,数据量变成了64
再经过一次线性层的处理,数据量变成了10(因为模型最后要从十个类别中预测一个,最后的数据可以是对应类别的对应概率)
后续我们拿到最终的10数据之后,就可以做一些逻辑处理,比如,如果最后的数据是对应类别的对应概率,那么我们取最大的即可
代码
这就是依据上面的CIFAR10 model的结构,搭建的神经网络
检验代码/推算参数的小技巧
检验代码:
torch提供了一个API,即ones,这个API可以帮我们构建随机的输入数据,我们只需要规定他的shape即可
这里我们规定输入数据的shape为(64, 3, 32, 32)
将其放入模型中进行处理,然后打印output的shape,可以看到输出两个数:左边是batch size,即抓包数,右边是图片的数量(一维),或者高和宽(二维)
这个可以帮助我们检验模型搭建的参数是否哪里设置的有问题,一旦有问题,这样运行就会报错
推算参数:
如果某一步我们不知道接下来该设置的参数的大小时,有些情况,我们可以在forward中,将步骤推进到未知参数函数的前一步,让其输出“截止到未知函数之前”的信息,就可以输出接下来该设置什么参数了
如,此处输出了1024:
Sequential的使用
他是搭建神经网络时,构造函数和forward函数的一个平替,使用他可以简化构造函数和forward函数
可以看到,我们直接在构造函数中,使用Sequential,然后,接下来的每一层的填入,都当做是Sequential的一个参数即可,我们还是照常设置参数,只不过无需任何的接收,按照顺序进行参数设置即可
之后,在forward中,也无需一个一个将x传入,而是直接调用Sequential返回的对象,将x传入即可
补充介绍
SummaryWriter对象还有一个API是add_graph,他可以将神经网络展示出来,
且双击其中每一步,可以具体看到神经网络的每个步骤,以及每个步骤的数据
以及输入数据的大小尺寸、一些参数等等
损失函数
介绍
损失函数,举个简单的例子,现在我们有一个实际的输出,还有一个目标
那么损失函数Loss,就会计算出我们现在的实际输出与我们的目标之间的差距,或者说误差,且可以告诉我们是哪个模块误差比较大,差的比较多,从而为我们调整训练方向、更好的训练出更合适的参数,提供一定的依据
文档
代码1
以L1Loss为例:
首先需要一对数据集,tensor类型(因为他有深度学习所需的属性)
分别表示一个实际输出和一个目标
我们需要将其shape修改成:
任意修改。。。
我们就将其修改为最常用的:抓取数、信道数、高、宽
之后创建L1Loss对象,将两个数据传进去即可得到一个结果,该结果默认情况下是取各个对应值的差值,然后取平均
如上图输出0.66…
当然我们可以修改他的reduction参数为‘sum’,那么最后的损失函数就是各个对应值的求和
代码2
MSELoss,损失函数的计算用“各个对应项的方差”
代码3(“分类”的损失函数)
假设,我们的结果是“三分类”,分别是人、狗、猫
然后,我们给进去一个input,然后给出一个三元组,就是最后的输出,表示各个分类的一个概率
而我们期望的目标结果是,能预测到第二个分类,对应下标也就是“1”
这样,x就是[0.1 , 0.2, 0.3]
class就是1
最后损失函数的计算见上图公式的最终推导结果
负的x的target下标对应的概率+log(e的各个概率的次方求和)
第一部分表示,对应目标分类的概率越高,损失函数的结果就越小,表示误差越小
第二部分表示,其概率差异越大,误差越小,概率越持平,误差越大,表示一个灵敏度
注意,其中的log是默认以10为底,所有log可以看成ln
可以看到,input(这里的输入其实就是模型训练出来的实际输出)的shape,可以设置为C,或者(N,C)
N是batch size,就是抓取数。而C是分类的类别的个数
Target的shape可以不设置,或者设置一个N即可
可以看到最后的输出是1.1019
与我们的计算结果相同
在神经网络当中的应用:
我们设置抓取器一次抓取一张图片,这样便于观察数据
我们利用之前的CIARF10的神经网络,拿到每个数据的inputs和labels之后,将inputs放入模型处理一下,拿到一个输出,直接打印输出结果,以及这张图真正的target(或者说label,因为label是已经标注好了的,其label就是这张图真正的类别,所以也就是这个数据的目标target)
可以看到输出结果是:10个概率(因为这个神经网络就是对十个类别做出判断,十选一)
图片的真正的target是[3]
如果抓包数为2:
一次循环计算两个数据,因为两个数据被打包在了一个data里,所以可以一次循环计算两个数据
计算出每个数据的损失函数的结果:
之后,我们看看如何让损失函数提供我们调整模型参数的依据:
得到损失函数的结果后,我们使用该结果,调用backward函数,就可以让损失函数为我们的神经网络的模型调整提供依据
我们在41打断点,进行调试(注意,这样调试运行的话,代码会执行完40行之后停下来)
我们找到神经网络模型中,某个卷积层的weight的grad:
模型参数的调整,实际上就是调整卷积层的卷积核的值,可以看到weight后面有五个数,那就是五个参数,之所以有五个,是因为我们设置卷积核的大小是5
而现在,还没有执行backward,所以grad是None,grad是梯度的意思,损失函数就是通过影响梯度值,从而影响到参数的调整,梯度值会很大程度上参与参数的调整
当我们下一步,执行了bacward之后:
可以看到grad被赋值了,所以,损失函数通过grad梯度值,为模型的参数调整提供依据,具体说,其中所要调整的参数包括“卷积层的卷积核的值”
优化器
作用
当我们根据损失函数计算出误差,并且使用backward对梯度进行了设置之后,就要用到优化器,优化器会根据梯度grad去进行参数的调整,也就是调参的动作是优化器完成的
文档
文档中会有使用示例
而下面的算法,就是各种不同的优化器,我们可以调用不同的算法,去创建不同的优化器,每个算法调用时,传参有所不同,具体可以点进去查看具体的传参,但是前两个参数都是需要传入“模型参数”、“学习速率”:(所以我们初学时,可以只传前两个参数先)
代码
我们可以在原先损失函数的模型基础上,进行一些添加
首先就是先根据一个算法,创建出一个优化器:
第一个参数传入模型对象的parameters()方法即可,第二个参数传入学习速率,一般0.01就可以
之后,在每次损失函数backward之后,就使用优化器去调参(optim.step()),但是注意,每次backward之前,都要使用优化器对象调用zero_grad()去将梯度清零,这是在每个循环步都进行梯度清零,避免当前循环步新的梯度产生影响
但是,经过上图的代码,我们只是对数据集中的每个包的数据都进行了一遍调参,也就是对整个数据集只进行了一轮调参
所以,我们可以更新代码,使得可以对数据集进行多轮调参:
我们对每一轮的损失函数计算出来的误差进行累加,最后输出,就可以量化的看到优化器的优化,误差越来越小
(当然,某些情况下会出现误差越来越大的情况,因为这是模型训练,不可能很简单的一维线性的降低,会受到多方面影响)
现有网络模型的使用和修改
文档
我们在Pytorch训练神经网络-入门一栏的最后末尾,也提到了pytorch给我们提供了各个领域的各个类别的模型,现在我们来学着使用他们
我们以视觉为例
进去之后就可以看到这些都是视觉方面的模型,上图展示的是“物体分类”方面的模型
右侧有着不同类别的模型索引
分别是“分类”、“语义分割”、“目标检测、实例分割”等等
使用
接下来就以分类的VGG模型为例:
第一个参数:可以指定使用哪个已经训练好的权重,即已经训练好了的参数,并且是多个参数,可以进行选择
(默认为无,也就是不使用训练好的权重(或者说参数),而是使用一些随机的初始值,后续再进行训练)
第二个参数:指定是否显示下载进度
代码
不进行预权重的指定:
可以看到,直接打印模型,可以打印出模型的框架、以及内容、以及各个层的形参设置
(注意,这里的形参参数与我们的训练参数不一样,这里是对模型的结构进行设置的参数,而我们平时说的参数是训练过程中,所调整的数据的权重,或者说函数的参数,二者不是一回事)
指定一个预处理的权重(即已经训练好了的权重):
如果指定了预处理完成的权重,那么会下载这些权重,然后同样会加载打印上面的模型结构
修改
假如说,我就是想用CIFAR10去训练这个模型:
可以从模型的结构看到,最终模型会输出1000个类型,而CIFAR10最终只有10个类型
所以,我们要对模型进行一些修改,或者在最后进行一些添加,
这里我们在最后添加一层线性层,使其输入为1000,输出为10:
使用add_module这个API,可以对现有的网络模型进行添加一些层,上图就是添加了线性层,使得最终的输出为10个类型
参数一是对添加的层的命名
参数二是这个层的构建
当然也可以添加一个Sequential,从而实现批量添加
同时,我们注意到,这个网络模型分为了三个部分,最后一部分叫做classifier,我们还可以指定添加到该部分的末尾:
当然也可以指定到其他部分的末尾:
这里可以知道,这个添加暂时还是一次性的,即保存在内存,进程结束的话,就会恢复原样,因为我们没有进行任何形式的硬盘保存,所以此处看到之前在classifter中添加的层没有了
同样的,我们还可以进行修改:
指出要修改哪一个部分的哪一个(使用下标索引),上图打印模型时,每一层前面的数字就是他的下标
我们修改第七个(下标为6)的线性层的输出为10
网络模型的保存和读取
方式1
保存
No.7:首先,需要从网络上拿到一个模型到程序中
之后,使用torch.save进行模型的保存
参数一:模型对象的名字
参数二:模型的保存路径以及保存文件的名字(如上图)
读取/加载
直接使用torch.load,参数传入模型文件的路径(精确到文件本身)
就可以加载模型,并且得到模型对象
注意,这种方法的保存和读取,都是模型结构+权重参数一起保存和读取的
陷阱
方式1有一个陷阱,或者说容易忽略的地方:
假如说我们保存的不是网络模型,而是自己手搓的模型,那么保存时,还是使用save进行保存
加载时:
可以看到,直接加载会报错
原因:当前加载的文件内没有Tudui这个模型的定义或者声明,文件找不到
解决:将我们的模型拷过来,或者进行文件的引用:
拷贝模型:
注意,只将类拷贝过来即可,无需再创建对象
引用文件:
Tudui这个类在model_save这个文件中,所以,就如上图所示进行文件的引用
方式2
保存
第二种保存时,同样是使用save这个方法,但是第一个传参是模型对象.state_dict(),表示获取模型的权重参数即可,将模型的权重参数保存到参数二的路径中
加载
由于模型文件中只保存了模型的权重参数,而没有保存模型的结构,所以,在加载模型时,只有权重参数是不够的,需要先加载模型的结构,所以,先创建模型的对象(对于网络模型,选择不使用预处理的权重参数),之后,调用模型对象的load_state_dict方法,传入“使用load加载出来的权重参数”
最后打印模型