文章目录
pytorch — 学习笔记
一、文档
- 文档:
- 官网:https://pytorch.org/
- 文档:
- https://apachecn.gitee.io/pytorch-doc-zh/#/
- https://pytorch.apachecn.org/
二、pytorch语法
(一) 辅助函数
-
dir():
返回某一个模块下面的子模块
import torch dir(torch) dir(torch.Tensor)
-
help():
返回某一个函数的说明文档
help(torch.cuda.is_available) 返回: Help on function is_available in module torch.cuda: is_available() Returns a bool indicating if CUDA is currently available.
(二)数据结构
- 张量Tensor:
三、pytorch用法
(一)自动求导
- 参考资料:https://zhuanlan.zhihu.com/p/69294347
- 核心点:
- requires_grad:torch.tensor(2.0, requires_grad=True)
- .grad:求导结果
- .grad_fn:
- forward():正向传播
- backward():反向传播,调用之后会计算所有梯度
1. 张量
-
torch.Tensor
是这个包的核心类。如果设置它的属性.requires_grad
为True
,那么autograd
将会追踪对于该张量的所有操作。当完成计算后可以通过调用.backward()
,来自动计算所有的梯度。这个张量的所有梯度将会自动累加到.grad
属性。- grad:求导结果。一般只有设置了.requires_grad为True的叶子张量该属性有值,其他张量该值为空。若其他张量需要保存求导结果,可使用
tensor.retain_grad()
,如:l4.retain_grad() - grad_fn:叶子张量的该属性为None
- grad:求导结果。一般只有设置了.requires_grad为True的叶子张量该属性有值,其他张量该值为空。若其他张量需要保存求导结果,可使用
-
Tensor
和Function
互相连接生成了一个无圈图(acyclic graph),它编码了完整的计算历史。每个张量都有一个.grad_fn
属性,该属性引用了创建Tensor
自身的Function
(除非这个张量是用户手动创建的,即这个张量的grad_fn
是None
)。 -
张量维度顺序:
【batchsize, channel, H, W】
-
keras张量顺序:
【batch_size, H, W, channel】
-
2. 梯度
- 反向传播
3. 计算图
计算图通常包含两种元素,一个是 tensor,另一个是 Function。
这里 Function 指的是在计算图中某个节点(node)所进行的运算,比如加减乘除卷积等等之类的,Function 内部有 forward()
和 backward()
两个方法,分别应用于正向、反向传播。
4. inplcae
inplace 指的是在不更改变量的内存地址的情况下,直接修改变量的值。
-
通过
tensor._version
来检测tensor 发生了 inplace 操作每次 tensor 在进行 inplace 操作时,变量
_version
就会加1,其初始值为0。在正向传播过程中,求导系统记录的b
的 version 是0,但是在进行反向传播的过程中,求导系统发现b
的 version 变成1了,所以就会报错了。但是还有一种特殊情况不会报错,就是反向传播求导的时候如果没用到b
的值(比如y=x+1
, y 关于 x 的导数是1,和 x 无关),自然就不会去对比b
前后的 version 了,所以不会报错。上边我们所说的情况是针对非叶子节点的,对于
requires_grad=True
的叶子节点来说,要求更加严格了,甚至在叶子节点被使用之前修改它的值都不行。-
如果在某种情况下需要重新对叶子变量赋值该怎么办呢:
a = torch.tensor([10., 5., 2., 3.], requires_grad=True) print(a, a.is_leaf) # tensor([10., 5., 2., 3.], requires_grad=True) True with torch.no_grad(): a[:] = 10. print(a, a.is_leaf) # tensor([10., 10., 10., 10.], requires_grad=True) True # torch.no_grad() 是一个上下文管理器,被该语句 wrap 起来的部分将不会track 梯度。 # 所以如果有不想被track的计算部分可以通过这么一个上下文管理器包裹起来。这样可以执行计算,但该计算不会在反向传播中被记录。
-
5. 动态图
所谓动态图,就是每次当我们搭建完一个计算图,然后在反向传播结束之后,整个计算图就在内存中被释放了。如果想再次使用的话,必须从头再搭一遍
(二)神经网络
- 参考资料:https://blog.csdn.net/zkk9527/article/details/88399176
1. torch.nn
-
可以使用
torch.nn
包来构建神经网络.我们已经介绍了
autograd
包,nn
包则依赖于autograd
包来定义模型并对它们求导。一个
nn.Module
包含各个层和一个forward(input)
方法,该方法返回output
。
2. 神经网络训练过程
一个神经网络的典型训练过程如下:
- 定义包含一些可学习参数(或者叫权重)的神经网络
- 在输入数据集上迭代
- 通过网络处理输入
- 计算loss(输出和正确答案的距离)
- 将梯度反向传播给网络的参数
- 更新网络的权重,一般使用一个简单的规则:
weight = weight - learning_rate * gradient
3. 编程实现
-
定义 神经网络 部分,步骤如下:
-
先定义一个Class,继承 nn.Module:
class Net(nn.Module): def __init__(self): super().__init__() self.conv1=nn.Conv2d(1,6,5) self.conv2=nn.Conv2d(6,16,5) def forward(self, x): x=F.max_pool2d(F.relu(self.conv1(x)),2) x=F.max_pool2d(F.relu(self.conv2(x)),2) return x
Class里面主要写两个函数,一个是初始化的__init__函数,另一个是forward函数。
-
init函数:
在这个里面主要就是定义卷积层的,神经网络“深度学习”其实主要就是学习卷积核里的参数,像别的不需要学习和改变的,就不用放进去。比如激活函数relu(),你非要放进去也行,再给它起个名字叫myrelu,也是可以的。
-
forward函数:
forward里面就是真正执行数据的流动。比如下面的代码,输入的x先经过定义的conv1(这个名字是你自己起的),再经过激活函数F.relu()(这个就不是自己起的名字了,最开始应该先import torch.nn.functional as F,F.relu()是官方提供的函数。
在一系列流动以后,最后把 x 返回。
-
-
使用定义好的 Net 类:
net=Net() # 在输入数据集上迭代,通过网络处理输入 output=net(input) #input应该定义成tensor类型
-
-
计算loss(网络输出和正确答案的距离):
-
定义损失函数,以MSEloss为例:
compute_loss=nn.MSELoss()
需要把这个函数赋一个实例,叫成compute_loss。之后就可以把你的神经网络的输出,和标签target传入进去,算出loss,下一步就是反向传播。
loss=compute_loss(target,output)
-
-
将梯度反向传播给网络的参数:
-
通过下述方式实现对W的更新:
- 先算 loss 对于输入 x 的偏导,(当然网络好几层,这个x指的是每一层的输入,而不是最开始的输入input)
- 对【1】的结果再乘以一个步长(这样就相当于是得到一个对参数W的修改量)
- 用W减掉这个修改量,完成一次对参数W的修改。
-
反向传播,计算梯度,这一步其实就是把上面【1】给算完了,得到对参数W一步的更新量,算是一次反向传播。
loss.backward()
-
-
定义优化器
-
选SGD方式为例:
from torch import optim optimizer=optim.SGD(net.parameters(),lr=0.001,momentum=0.9)
同样,优化器也是一个类,先定义一个实例optimizer。注意在optimizer定义的时候,需要给SGD传入了net的参数parameters,这样之后优化器就掌握了对网络参数的控制权,就能够对它进行修改了。传入的时候把学习率lr也传入了。
在每次迭代之前,先把optimizer里存的梯度清零一下(因为W已经更新过的“更新量”下一次就不需要用了)
optimizer.zero_grad()
在loss.backward()反向传播以后,更新参数:
optimizer.step()
-
-
总结网络搭建的过程:
-
先定义网络:编写网络 Net 的 Class,声明网络的实例net=Net(),
-
定义优化器 optimizer = optim.xxx(net.parameters(),lr=xxx),
-
再定义损失函数(自己写class或者直接用官方的,compute_loss=nn.MSELoss()或者其他。
-
在定义完之后,开始一次一次的循环:
①先清空优化器里的梯度信息,optimizer.zero_grad(); ②再将input传入,output=net(input) ,正向传播 ③算损失,loss=compute_loss(target,output) ##这里target就是参考标准值GT,需要自己准备,和之前传入的input一一对应 ④误差反向传播,loss.backward() ⑤更新参数,optimizer.step()
这样就实现了一个基本的神经网络。大部分神经网络的训练都可以简化为这个过程,无非是传入的内容复杂,网络定义复杂,损失函数复杂,等等等等。
-
4. 补充知识点
-
nn.Sequential vs nn.ModuleList
参考资料:https://zhuanlan.zhihu.com/p/75206669
- nn.Sequential:
- nn.Sequential里面的模块按照顺序进行排列的,所以必须确保前一个模块的输出大小和下一个模块的输入大小是一致的。
- nn.Sequential中可以使用OrderedDict来指定每个module的名字,而不是采用默认的命名方式(按序号 0,1,2,3…)。
- nn.ModuleList:
- nn.ModuleList,它是一个储存不同 module,并自动将每个 module 的 parameters 添加到网络之中的容器。
- 你可以把任意 nn.Module 的子类 (比如 nn.Conv2d, nn.Linear 之类的) 加到这个 list 里面,方法和 Python 自带的 list 一样,无非是 extend,append 等操作。但不同于一般的 list,加入到 nn.ModuleList 里面的 module 是会自动注册到整个网络上的,同时 module 的 parameters 也会自动添加到整个网络中。
- nn.Sequential:
3. 加载数据
- dataloader
4. 开始训练
- 首先,我们要确保我们的网络处于训练模式。然后,每个epoch对所有训练数据进行一次迭代。加载单独批次由DataLoader处理。
- 首先,我们需要使用optimizer.zero_grad()手动将梯度设置为零,因为PyTorch在默认情况下会累积梯度。
- 然后,我们生成网络的输出(前向传递),并计算输出与真值标签之间的负对数概率损失。
- 现在,我们收集一组新的梯度,并使用optimizer.step()将其传播回每个网络参数。有关PyTorch自动渐变系统内部工作方式的详细信息,请参阅autograd的官方文档(强烈推荐)。
- 使用一些打印输出来跟踪进度。为了在以后创建一个良好的培训曲线,我们还创建了两个列表来节省培训和测试损失。在x轴上,我们希望显示网络在培训期间看到的培训示例的数量。
5. 语法
-
model.eval():
使用PyTorch进行训练和测试时一定注意要把实例化的model指定train/eval,eval()时,框架会自动把BN和DropOut固定住,不会取平均,而是用训练好的值,不然的话,一旦test的batch_size过小,很容易就会被BN层导致生成图片颜色失真极大