清理库存5~
https://github.com/zergtant/pytorch-handbook/blob/master/chapter1/3_neural_networks_tutorial.ipynb
使用torch.nn包来构建神经网络。nn
包依赖autograd
包来定义模型并求导。 一个nn.Module
包含各个层和一个forward(input)
方法,该方法返回output
。
神经网络的典型训练过程如下:
- 定义包含一些可学习的参数(或者叫权重)神经网络模型;
- 在数据集上迭代;
- 通过神经网络处理输入;
- 计算损失(输出结果和正确值的差值大小);
- 将梯度反向传播回网络的参数;
- 更新网络的参数,主要使用如下简单的更新原则:
weight = weight - learning_rate * gradient
在模型中必须要定义 forward
函数,backward
函数(用来计算梯度)会被autograd
自动创建。 可以在 forward
函数中使用任何针对 Tensor 的操作。
net.parameters()
返回可被学习的参数(权重)列表和值.
使用PyTorch计算梯度数值
在张量(Tensor类)上的所有操作,Autograd都能为他们自动提供微分,简化了手动计算导数的复杂过程。
在0.4以前的版本中,Pytorch使用Variable类来自动计算所有的梯度。Variable类主要包含三个属性:
- data:保存Variable所包含的Tensor;
- grad:保存data对应的梯度,grad也是个Variable,而不是Tensor,它和data的形状一样;
- grad_fn:指向一个Function对象,这个Function用来反向传播计算输入的梯度。
![f3546e99ab788641b59f976b0f1de4dd.png](https://i-blog.csdnimg.cn/blog_migrate/36ba4a53ed92e02b5241e6138cd2585b.png)
![682ffd942c82e9efce89fc2af84629f9.png](https://i-blog.csdnimg.cn/blog_migrate/18bdd91757cfe7c70efee54cfbb35134.png)
梯度
反向传播 因为 out
是一个纯量(scalar),out.backward()
等于out.backward(torch.tensor(1))
。
![91a6c57395dda9107a12e6d31946cb49.png](https://i-blog.csdnimg.cn/blog_migrate/f3dbf626338158f985ba3ea16d1ec6af.png)
![08b7d5dcad053a8b452fdfd6715ebb8c.png](https://i-blog.csdnimg.cn/blog_migrate/699d3f4b28ca0f6f0d52096b173dff82.png)
从0.4起, Variable 正式合并入Tensor类, 通过Variable嵌套实现的自动微分功能已经整合进入了Tensor类中。虽然为了代码的兼容性还是可以使用Variable(tensor)这种方式进行嵌套, 但是这个操作其实什么都没做。
所以,以后的代码建议直接使用Tensor类进行操作,因为官方文档中已经将Variable设置成过期模块。要想通过Tensor类本身就支持了使用autograd功能,只需要设置.requries_grad=True。
Variable类中的的grad和grad_fn属性已经整合进入了Tensor类中。
Autograd
在张量创建时,通过设置 requires_grad 标识为Ture来告诉Pytorch需要对该张量进行自动求导,PyTorch会记录该张量的每一步操作历史并自动计算。
PyTorch会自动追踪和记录对与张量的所有操作,当计算完成后调用.backward()方法自动计算梯度并且将计算结果保存到grad属性中。
Automatic differentiation package - torch.autogradpytorch.org简单的自动求导:
grad:保存data对应的梯度,grad也是个Variable,而不是Tensor,它和data的形状一样;
![296c09f51eeffddbdf38c386ebcbc22b.png](https://i-blog.csdnimg.cn/blog_migrate/ecc761c2a2271cc5a7bf1ffa506ade28.jpeg)
如果Tensor类表示的是一个标量(即它包含一个元素的张量),则不需要为backward()指定任何参数,但是如果它有更多的元素,则需要指定一个gradient参数,它是形状匹配的张量。 以上的z.backward()
相当于是z.backward(torch.tensor(1.))
的简写。 这种参数常出现在图像分类中的单标签分类,输出一个标量代表图像的标签。
也即:
如果需要计算导数,可以在Tensor
上调用.backward()
。 如果Tensor
是一个标量(即它包含一个元素数据)则不需要为backward()
指定任何参数, 但是如果它有更多的元素,你需要指定一个gradient
参数来匹配张量的形状。
![c1a472369398cff6a26b4c4af7ef22f3.png](https://i-blog.csdnimg.cn/blog_migrate/a462a60fc4a249673fd45ff87f25ab1e.jpeg)
可以使用with torch.no_grad()上下文管理器临时禁止对已设置requires_grad=True的张量进行自动求导。这个方法在测试集计算准确率的时候会经常用到,例如:
![59cfd37461af34ebbf8e94aabf0c3e47.png](https://i-blog.csdnimg.cn/blog_migrate/89019d9455ccac9db2f39c4ff05deadc.png)
使用.no_grad()进行嵌套后,代码不会跟踪历史记录,也就是说保存的这部分记录会减少内存的使用量并且会加快少许的运算速度。
Autograd 过程解析
为了说明Pytorch的自动求导原理,我们来尝试分析一下PyTorch的源代码,虽然Pytorch的 Tensor和 TensorBase都是使用CPP来实现的,但是可以使用一些Python的一些方法查看这些对象在Python的属性和状态。 Python的 dir()
返回参数的属性、方法列表。z
是一个Tensor变量,看看里面有哪些成员变量。
一个变量是创建变量还是结果变量是通过.is_leaf
来获取的。
为什么我们执行z.backward()
方法会更新x.grad
和y.grad
呢? .grad_fn
属性记录的就是这部分的操作,虽然.backward()
方法也是CPP实现的,但是可以通过Python来进行简单的探索。grad_fn
:记录并且编码了完整的计算历史。
在PyTorch的反向图计算中,AccumulateGrad
类型代表的就是叶子节点类型,也就是计算图终止节点。AccumulateGrad
类中有一个.variable
属性指向叶子节点。
这样整个规程就很清晰了:
- 当我们执行z.backward()的时候。这个操作将调用z里面的grad_fn这个属性,执行求导的操作。
- 这个操作将遍历grad_fn的next_functions,然后分别取出里面的Function(AccumulateGrad),执行求导操作。这部分是一个递归的过程直到最后类型为叶子节点。
- 计算出结果以后,将结果保存到他们对应的variable 这个变量所引用的对象(x和y)的 grad这个属性里面。
- 求导结束。所有的叶节点的grad变量都得到了相应的更新
扩展Autograd
如果需要自定义autograd扩展新的功能,就需要扩展Function类。因为Function使用autograd来计算结果和梯度,并对操作历史进行编码。 在Function类中最主要的方法就是forward()
和backward()
他们分别代表了前向传播和反向传播。
一个自定义的Function需要一下三个方法:
__init__ (optional):如果这个操作需要额外的参数则需要定义这个Function的构造函数,不需要的话可以忽略。 forward():执行前向传播的计算代码 backward():反向传播时梯度计算的代码。 参数的个数和forward返回值的个数一样,每个参数代表传回到此操作的梯度。
https://github.com/zergtant/pytorch-handbook/blob/master/chapter2/2.1.2-pytorch-basics-autograd.ipynb 测试
Note
``torch.nn`` 只支持小批量输入。整个 ``torch.nn`` 包都只支持小批量样本,而不支持单个样本。 例如,``nn.Conv2d`` 接受一个4维的张量, ``每一维分别是sSamples * nChannels * Height * Width(样本数*通道数*高*宽)``。 如果你有单个样本,只需使用 ``input.unsqueeze(0)`` 来添加其它的维数
回顾:
torch.Tensor
:一个用过自动调用backward()
实现支持自动梯度计算的 多维数组 , 并且保存关于这个向量的梯度 w.r.t.nn.Module
:神经网络模块。封装参数、移动到GPU上运行、导出、加载等。nn.Parameter
:一种变量,当把它赋值给一个Module
时,被 自动 地注册为一个参数。autograd.Function
:实现一个自动求导操作的前向和反向定义,每个变量操作至少创建一个函数节点,每一个Tensor
的操作都回创建一个接到创建Tensor
和 编码其历史 的函数的Function
节点。
重点如下:
- 定义一个网络
- 处理输入,调用backword
损失函数
一个损失函数接受一对 (output, target) 作为输入,计算一个值来估计网络的输出和目标值相差多少。
nn包中有很多不同的损失函数。nn.MSELoss
是一个比较简单的损失函数,它计算输出和目标间的均方误差.
反向传播
调用loss.backward()获得反向传播的误差。
但是在调用前需要清除已存在的梯度,否则梯度将被累加到已存在的梯度。
更新权重
在实践中最简单的权重更新规则是随机梯度下降(SGD):
``weight = weight - learning_rate * gradient``
训练一个分类器
对于图像任务,我们创建了一个包 torchvision
,它包含了处理一些基本图像数据集的方法。这些数据集包括 Imagenet, CIFAR10, MNIST 等。除了数据加载以外,torchvision
还包含了图像转换器, torchvision.datasets
和 torch.utils.data.DataLoader
。
torchvision
包不仅提供了巨大的便利,也避免了代码的重复。
训练一个图像分类器
依次按照下列顺序进行:
- 使用
torchvision
加载和归一化CIFAR10训练集和测试集 - 定义一个卷积神经网络
- 定义损失函数
- 在训练集上训练网络
- 在测试集上测试网络
在GPU上训练
把一个神经网络移动到GPU上训练就像把一个Tensor转换GPU上一样简单。并且这个操作会递归遍历有所模块,并将其参数和缓冲区转换为CUDA张量。
多GPU数据并行
PyTorch非常容易就可以使用多GPU,用如下方式把一个模型放到GPU上:
device = torch.device("cuda:0")
model.to(device)
GPU: 然后复制所有的张量到GPU上:
mytensor = my_tensor.to(device)
请注意,只调用my_tensor.to(device)
并没有复制张量到GPU上,而是返回了一个copy。所以你需要把它赋值给一个新的张量并在GPU上使用这个张量。
在多GPU上执行前向和反向传播是自然而然的事。 但是PyTorch默认将只使用一个GPU。
使用DataParallel
可以轻易的让模型并行运行在多个GPU上。
model = nn.DataParallel(model)
注意:
DataParallel
能在任何模型(CNN,RNN,Capsule Net等)上使用。
总结
DataParallel会自动的划分数据,并将作业发送到多个GPU上的多个模型。 并在每个模型完成作业后,收集合并结果并返回。
更多信息请看这里: https://pytorch.org/tutorials/beginner/former_torchies/parallelism_tutorial.html.
##############未完待续################